/* * Copyright (C) 2006-2011 IsmAvatar <IsmAvatar@gmail.com> * Copyright (C) 2006, 2007 TGMG <thegamemakerguru@gmail.com> * Copyright (C) 2007, 2008 Quadduc <quadduc@gmail.com> * Copyright (C) 2006, 2007, 2008 Clam <clamisgood@gmail.com> * Copyright (C) 2013, 2014, 2015 Robert B. Colton * * This file is part of LateralGM. * * LateralGM is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * LateralGM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License (COPYING) for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.lateralgm.main; import static javax.swing.GroupLayout.PREFERRED_SIZE; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.SplashScreen; import java.awt.Toolkit; import java.awt.Window; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.StringSelection; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.InputEvent; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList; import java.util.Enumeration; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Scanner; import java.util.Vector; import java.util.jar.JarFile; import java.util.jar.Manifest; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.AbstractAction; import javax.swing.AbstractButton; import javax.swing.Action; import javax.swing.Box; import javax.swing.DefaultComboBoxModel; import javax.swing.DropMode; import javax.swing.GroupLayout; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.InputMap; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JComponent; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JInternalFrame; import javax.swing.JLabel; import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JProgressBar; import javax.swing.JRootPane; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.JTree; import javax.swing.KeyStroke; import javax.swing.LookAndFeel; import javax.swing.RootPaneContainer; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.UnsupportedLookAndFeelException; import javax.swing.GroupLayout.Alignment; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.plaf.metal.DefaultMetalTheme; import javax.swing.plaf.metal.MetalLookAndFeel; import javax.swing.plaf.metal.OceanTheme; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import javax.swing.tree.TreeSelectionModel; import org.lateralgm.components.ActionList; import org.lateralgm.components.CodeTextArea; import org.lateralgm.components.CustomJToolBar; import org.lateralgm.components.ErrorDialog; import org.lateralgm.components.GmMenuBar; import org.lateralgm.components.GmTreeGraphics; import org.lateralgm.components.impl.CustomFileFilter; import org.lateralgm.components.impl.FramePrefsHandler; import org.lateralgm.components.impl.GmTreeEditor; import org.lateralgm.components.impl.ResNode; import org.lateralgm.components.mdi.MDIPane; import org.lateralgm.components.visual.HintTextField; import org.lateralgm.file.ProjectFile; import org.lateralgm.file.ProjectFile.ResourceHolder; import org.lateralgm.file.ProjectFile.SingletonResourceHolder; import org.lateralgm.file.ResourceList; import org.lateralgm.file.iconio.ICOFile; import org.lateralgm.messages.Messages; import org.lateralgm.resources.Constants; import org.lateralgm.resources.GameSettings; import org.lateralgm.resources.GmObject; import org.lateralgm.resources.InstantiableResource; import org.lateralgm.resources.Resource; import org.lateralgm.resources.ResourceReference; import org.lateralgm.resources.Room; import org.lateralgm.resources.Script; import org.lateralgm.resources.Shader; import org.lateralgm.resources.Timeline; import org.lateralgm.resources.library.LibManager; import org.lateralgm.resources.sub.Argument; import org.lateralgm.resources.sub.Event; import org.lateralgm.resources.sub.Instance; import org.lateralgm.resources.sub.Instance.PInstance; import org.lateralgm.resources.sub.MainEvent; import org.lateralgm.resources.sub.Moment; import org.lateralgm.subframes.ActionFrame; import org.lateralgm.subframes.CodeFrame; import org.lateralgm.subframes.ConfigurationManager; import org.lateralgm.subframes.ConstantsFrame; import org.lateralgm.subframes.EventPanel; import org.lateralgm.subframes.ExtensionPackagesFrame; import org.lateralgm.subframes.GameInformationFrame; import org.lateralgm.subframes.GameSettingFrame; import org.lateralgm.subframes.GmObjectFrame; import org.lateralgm.subframes.PreferencesFrame; import org.lateralgm.subframes.ResourceFrame; import org.lateralgm.subframes.ResourceFrame.ResourceFrameFactory; import org.lateralgm.subframes.RoomFrame; import org.lateralgm.subframes.ScriptFrame; import org.lateralgm.subframes.ShaderFrame; import org.lateralgm.subframes.TimelineFrame; public final class LGM { public static final String version = "1.8.7.18"; //$NON-NLS-1$ // TODO: This list holds the class loader for any loaded plugins which should be // cleaned up and closed when the application closes. public static ArrayList<URLClassLoader> classLoaders = new ArrayList<URLClassLoader>(); public static boolean LOADING_PROJECT = false; public static JDialog progressDialog = null; public static JProgressBar progressDialogBar = null; public static String iconspath = "org/lateralgm/icons/"; //$NON-NLS-1$ public static String iconspack = "Calico"; //$NON-NLS-1$ public static String themename = "Swing"; //$NON-NLS-1$ public static boolean themechanged = false; public static int javaVersion; public static File tempDir, workDir; static { //Get Java Version String jv = System.getProperty("java.version"); //$NON-NLS-1$ Scanner s = new Scanner(jv); s.useDelimiter("[\\._-]"); //$NON-NLS-1$ javaVersion = s.nextInt() * 10000 + s.nextInt() * 100 + s.nextInt(); s.close(); try { workDir = new File(LGM.class.getProtectionDomain().getCodeSource().getLocation().toURI()); } catch (Exception e) { System.err.println(Messages.format("LGM.NO_WORKDIR",e.getClass(),e.getLocalizedMessage())); //$NON-NLS-1$ } } public static JFrame frame; public static JPanel contents; public static JToolBar tool; public static JTree tree; public static ResNode root; public static ProjectFile currentFile = new ProjectFile(); public static MDIPane mdi; private static ConstantsFrame constantsFrame; private static GameInformationFrame gameInfo; private static GameSettingFrame gameSet; private static ExtensionPackagesFrame extSet; public static EventPanel eventSelect; private static JFrame eventFrame; public static AbstractButton eventButton; public static PreferencesFrame prefFrame; public static Cursor zoomCursor; public static Cursor zoomInCursor; public static Cursor zoomOutCursor; private static String progressTitle; public static GmMenuBar menuBar; public static JToolBar filterPanel; private static JTextField filterText; private static JCheckBox wholeWordCB; private static JCheckBox matchCaseCB; private static JCheckBox regexCB; private static JCheckBox pruneResultsCB; private static JButton closeButton; private static JTree searchTree; public static JComboBox<GameSettings> configsCombo; public static JDialog getProgressDialog() { if (progressDialog == null) { progressDialog = new JDialog(LGM.frame,true); progressDialogBar = new JProgressBar(0,140); progressDialogBar.setStringPainted(true); progressDialogBar.setPreferredSize(new Dimension(240,20)); progressDialog.add(BorderLayout.CENTER,progressDialogBar); progressDialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); progressDialog.pack(); progressDialog.setLocationRelativeTo(LGM.frame); progressDialog.setResizable(false); } return progressDialog; } public static JProgressBar getProgressDialogBar() { return progressDialogBar; } public static void setProgressDialogVisible(final boolean visible) { if (progressDialog != null) { progressDialogBar.setValue(0); progressDialog.setVisible(visible); return; } getProgressDialog().setVisible(visible); } public static void setProgressTitle(String title) { progressTitle = title; } public static void setProgress(final int value, final String message) { progressDialog.setTitle(progressTitle + " - " + message); //$NON-NLS-1$ progressDialogBar.setValue(value); } private static void createMouseCursors() { Toolkit toolkit = Toolkit.getDefaultToolkit(); Image cimg = LGM.getIconForKey("CursorDisplay.ZOOM").getImage(); //$NON-NLS-1$ BufferedImage img = new BufferedImage(32,32,BufferedImage.TYPE_INT_ARGB); Graphics g = img.createGraphics(); g.drawImage(cimg,0,0,null); zoomCursor = toolkit.createCustomCursor(img,new Point(0,0),"Zoom"); cimg = LGM.getIconForKey("CursorDisplay.ZOOM_IN").getImage(); //$NON-NLS-1$ img = new BufferedImage(32,32,BufferedImage.TYPE_INT_ARGB); g = img.createGraphics(); g.drawImage(cimg,0,0,null); zoomInCursor = toolkit.createCustomCursor(img,new Point(0,0),"ZoomIn"); cimg = LGM.getIconForKey("CursorDisplay.ZOOM_OUT").getImage(); //$NON-NLS-1$ img = new BufferedImage(32,32,BufferedImage.TYPE_INT_ARGB); g = img.createGraphics(); g.drawImage(cimg,0,0,null); zoomOutCursor = toolkit.createCustomCursor(img,new Point(0,0),"ZoomOut"); } public static void setLookAndFeel(String LOOKANDFEEL) { if (LOOKANDFEEL.equals(themename) && !LOOKANDFEEL.equals("Custom")) { themechanged = false; return; } themechanged = true; themename = LOOKANDFEEL; String lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); if (LOOKANDFEEL != null) { if (LOOKANDFEEL.equals("Swing")) { lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); // This theme is also known as Metal - Ocean lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel"; //$NON-NLS-1$ MetalLookAndFeel.setCurrentTheme(new OceanTheme()); } else if (LOOKANDFEEL.equals("Native")) { lookAndFeel = UIManager.getSystemLookAndFeelClassName(); } else if (LOOKANDFEEL.equals("Nimbus")) { lookAndFeel = "javax.swing.plaf.nimbus.NimbusLookAndFeel"; //$NON-NLS-1$ } else if (LOOKANDFEEL.equals("Windows")) { lookAndFeel = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; //$NON-NLS-1$ } else if (LOOKANDFEEL.equals("Windows Classic")) { lookAndFeel = "com.sun.java.swing.plaf.windows.WindowsClassicLookAndFeel"; //$NON-NLS-1$ // Fixes UI bug in the JDK where the buttons look way too big and get cut off. // https://bugs.openjdk.java.net/browse/JDK-8140527 UIManager.put("InternalFrame.titleButtonWidth", 22); //$NON-NLS-1$ UIManager.put("InternalFrame.titleButtonHeight", 22); //$NON-NLS-1$ } else if (LOOKANDFEEL.equals("CDE/Motif")) { lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; //$NON-NLS-1$ } else if (LOOKANDFEEL.equals("Metal")) { lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel"; //$NON-NLS-1$ MetalLookAndFeel.setCurrentTheme(new DefaultMetalTheme()); } else if (LOOKANDFEEL.equals("Ocean")) { lookAndFeel = "javax.swing.plaf.metal.MetalLookAndFeel"; //$NON-NLS-1$ MetalLookAndFeel.setCurrentTheme(new OceanTheme()); } else if (LOOKANDFEEL.equals("GTK+")) { lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel"; //$NON-NLS-1$ } else if (LOOKANDFEEL.equals("Custom")) { lookAndFeel = Prefs.swingThemePath; } else { // Perhaps we did not get the name right, see if the theme is installed // and attempt to use it. boolean foundMatch = false; LookAndFeelInfo lnfs[] = UIManager.getInstalledLookAndFeels(); for (int i = 0; i < lnfs.length; i++) { if (LOOKANDFEEL.equals(lnfs[i].getName())) { lookAndFeel = lnfs[i].getClassName(); foundMatch = true; } } if (!foundMatch) { System.err.println("Unexpected value of LOOKANDFEEL specified: " + LOOKANDFEEL); lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName(); } } try { UIManager.setLookAndFeel(lookAndFeel); } catch (ClassNotFoundException e) { System.err.println("Couldn't find class for specified look and feel:" + lookAndFeel); System.err.println("Did you include the L&F library in the class path?"); System.err.println("Using the default look and feel."); } catch (UnsupportedLookAndFeelException e) { System.err.println("Can't use the specified look and feel (" + lookAndFeel + ") on this platform."); System.err.println("Using the default look and feel."); } catch (Exception e) { System.err.println("Couldn't get specified look and feel (" + lookAndFeel + "), for some reason."); System.err.println("Using the default look and feel."); e.printStackTrace(); } } } // this function is for updating the look and feel after its // already been initialized and all controls created public static void updateLookAndFeel() { if (!themechanged) { return; } SwingUtilities.updateComponentTreeUI(tree); if (eventFrame == null) { SwingUtilities.updateComponentTreeUI(eventSelect); eventSelect.updateUI(); } else { SwingUtilities.updateComponentTreeUI(eventFrame); } Window windows[] = Window.getWindows(); for (Window window : windows) { SwingUtilities.updateComponentTreeUI(window); } } public static ConstantsFrame getConstantsFrame() { return constantsFrame; } public static GameInformationFrame getGameInfo() { return gameInfo; } public static GameSettingFrame getGameSettings() { return gameSet; } public static ExtensionPackagesFrame getExtensionPackages() { return extSet; } public static void showConstantsFrame(final Constants cnsts) { Runnable run = new Runnable() { @Override public void run() { if (constantsFrame.res != cnsts && constantsFrame.resOriginal != cnsts) { constantsFrame.res = cnsts; constantsFrame.resOriginal = cnsts.clone(); constantsFrame.revertResource(); } constantsFrame.updateTitle(); getConstantsFrame().setVisible(true); getConstantsFrame().toTop(); } }; constantsFrame.doDefaultCloseAction(run); } public static void showGameInformation() { getGameInfo().setVisible(true); getGameInfo().toTop(); } public static void showGameSettings(final GameSettings set) { Runnable run = new Runnable() { @Override public void run() { if (gameSet.res != set && gameSet.resOriginal != set) { gameSet.res = set; gameSet.resOriginal = set.clone(); gameSet.revertResource(); } gameSet.updateTitle(); getGameSettings().setVisible(true); getGameSettings().toTop(); } }; gameSet.doDefaultCloseAction(run); } public static void showExtensionPackages() { getExtensionPackages().setVisible(true); getExtensionPackages().toTop(); } private LGM() { } public static ImageIcon findIcon(String filename) { String custompath = Prefs.iconPath + filename; String jarpath = iconspath + iconspack + '/' + filename; String location = ""; //$NON-NLS-1$ if (Prefs.iconPack.equals("Custom")) { if (new File(custompath).exists()) { location = custompath; } else { location = jarpath; } } else { location = jarpath; } ImageIcon ico = new ImageIcon(location); if (ico.getIconWidth() == -1) { URL url = LGM.class.getClassLoader().getResource(location); if (url != null) { ico = new ImageIcon(url); } } return ico; } public static ImageIcon getIconForKey(String key) { Properties iconProps = new Properties(); InputStream is = LGM.class.getClassLoader().getResourceAsStream( "org/lateralgm/main/icons.properties"); //$NON-NLS-1$ try { if (is == null) throw new IOException(); iconProps.load(is); } catch (IOException e) { System.err.println("Unable to read icons.properties"); } String filename = iconProps.getProperty(key,""); //$NON-NLS-1$ if (!filename.isEmpty()) return findIcon(filename); return null; } public static JButton makeButton(String key) { JButton but = new JButton(); makeButton(but,key); return but; } public static AbstractButton makeButton(AbstractButton but, String key) { Icon ico = LGM.getIconForKey(key); if (ico != null) but.setIcon(ico); else but.setIcon(GmTreeGraphics.getBlankIcon()); but.setActionCommand(key); but.setToolTipText(Messages.getString(key)); but.addActionListener(Listener.getInstance()); return but; } private static JToolBar createToolBar() { tool = new JToolBar(); tool.setFloatable(true); tool.add(makeButton("Toolbar.NEW")); //$NON-NLS-1$ tool.add(makeButton("Toolbar.OPEN")); //$NON-NLS-1$ tool.add(makeButton("Toolbar.SAVE")); //$NON-NLS-1$ tool.add(makeButton("Toolbar.SAVEAS")); //$NON-NLS-1$ tool.addSeparator(); for (Class<? extends Resource<?,?>> k : Resource.kinds) if (InstantiableResource.class.isAssignableFrom(k)) { Icon ico = ResNode.ICON.get(k); if (ico == null) ico = GmTreeGraphics.getBlankIcon(); JButton but = new JButton(ico); but.setToolTipText(Messages.format("Toolbar.ADD",Resource.kindNames.get(k))); //$NON-NLS-1$ but.addActionListener(new Listener.ResourceAdder(false,k)); tool.add(but); } tool.addSeparator(); tool.add(makeButton("Toolbar.CST")); //$NON-NLS-1$ tool.add(makeButton("Toolbar.GMI")); //$NON-NLS-1$ tool.add(makeButton("Toolbar.PKG")); //$NON-NLS-1$ tool.addSeparator(); tool.add(new JLabel(Messages.getString("Toolbar.CONFIGURATIONS"))); //$NON-NLS-1$ configsCombo = new JComboBox<GameSettings>(); configsCombo.setModel(new DefaultComboBoxModel<GameSettings>(LGM.currentFile.gameSettings)); configsCombo.setMaximumSize(configsCombo.getPreferredSize()); tool.add(configsCombo); tool.add(makeButton("Toolbar.CONFIG_MANAGE")); //$NON-NLS-1$ tool.addSeparator(); tool.add(makeButton("Toolbar.GMS")); //$NON-NLS-1$ tool.addSeparator(); tool.add(makeButton("Toolbar.PREFERENCES")); //$NON-NLS-1$ tool.add(makeButton("Toolbar.DOCUMENTATION")); //$NON-NLS-1$ tool.add(Box.createHorizontalGlue()); //right align after this tool.add(eventButton = makeButton(new JToggleButton(),"Toolbar.EVENT_BUTTON")); //$NON-NLS-1$ return tool; } private static JTree createTree() { return createTree(newRoot()); } private static JTree createTree(ResNode newroot) { InvisibleTreeModel ml = new InvisibleTreeModel(newroot); ml.activateFilter(false); tree = new JTree(ml); GmTreeGraphics renderer = new GmTreeGraphics(); GmTreeEditor editor = new GmTreeEditor(tree,renderer); editor.addCellEditorListener(Listener.getInstance()); tree.setEditable(true); tree.addMouseListener(Listener.getInstance().mListener); if (javaVersion >= 10600) { tree.setTransferHandler(Listener.getInstance()); tree.setDragEnabled(true); tree.setDropMode(DropMode.ON_OR_INSERT); } tree.setCellRenderer(renderer); tree.setRootVisible(false); tree.setShowsRootHandles(true); tree.setCellEditor(editor); tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); //remove the cut, copy, and paste bindings InputMap im = tree.getInputMap(); for (KeyStroke s : im.allKeys()) { Object o = im.get(s); if (o.equals("cut") || o.equals("copy") || o.equals("paste")) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ im.put(s,"none"); //null doesn't remove them //$NON-NLS-1$ } return tree; } public static ResNode newRoot() { return root = new ResNode("Root",(byte) 0,null,null); //$NON-NLS-1$ } private static JComponent createMDI() { mdi = new MDIPane(); JScrollPane scroll = new JScrollPane(mdi); mdi.setScrollPane(scroll); scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); mdi.setBackground(Color.GRAY); return scroll; } public static void addURL(URL url) throws Exception { URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader(); Class<?> clazz = URLClassLoader.class; // Use reflection Method method= clazz.getDeclaredMethod("addURL", new Class[] { URL.class }); //$NON-NLS-1$ method.setAccessible(true); method.invoke(classLoader, new Object[] { url }); } public static void loadLookAndFeels() { if (workDir == null) return; File dir = new File(workDir,"lookandfeels"); //$NON-NLS-1$ if (!dir.exists()) { dir = new File(workDir.getParent(),"lookandfeels"); //$NON-NLS-1$ } File[] ps = dir.listFiles(new CustomFileFilter(null,".jar")); //$NON-NLS-1$ if (ps == null) return; for (File f : ps) { if (!f.exists()) continue; try { addURL(f.toURI().toURL()); } catch (Exception e) { e.printStackTrace(); } } } public static void loadPlugins() { if (workDir == null) return; File dir = new File(workDir.getParent(),"plugins"); //$NON-NLS-1$ if (!dir.exists()) dir = new File(workDir.getParent(),"Plugins"); //$NON-NLS-1$ File[] ps = dir.listFiles(new CustomFileFilter(null,".jar")); //$NON-NLS-1$ if (ps == null) return; for (File f : ps) { if (!f.exists()) continue; try { String pluginEntry = "LGM-Plugin"; //$NON-NLS-1$ JarFile jar = new JarFile(f); Manifest mf = jar.getManifest(); jar.close(); String clastr = mf.getMainAttributes().getValue(pluginEntry); if (clastr == null) throw new Exception(Messages.format("LGM.PLUGIN_MISSING_ENTRY",pluginEntry)); //$NON-NLS-1$ URLClassLoader ucl = new URLClassLoader(new URL[] { f.toURI().toURL() }); ucl.loadClass(clastr).newInstance(); classLoaders.add(ucl); } catch (Exception e) { String msgInd = "LGM.PLUGIN_LOAD_ERROR"; //$NON-NLS-1$ LGM.showDefaultExceptionHandler(new Exception(Messages.format(msgInd,f.getName()), e)); continue; } } } public static void populateTree() { /* TODO: This method here does not give the top level nodes for Game Info, Extensions, and * Settings a proper resource reference, they get null. My commented code here will give them * their proper references, but when a reload happens the references are lost again. * I seriously do not believe there should be nodes with null references in the tree. for (Class<? extends Resource<?,?>> k : Resource.kinds) { String name = Resource.kindNamesPlural.get(k); byte status = InstantiableResource.class.isAssignableFrom(k) ? ResNode.STATUS_PRIMARY : ResNode.STATUS_SECONDARY; if (status == ResNode.STATUS_SECONDARY) { SingletonResourceHolder<?> rh = (SingletonResourceHolder<?>) LGM.currentFile.resMap.get(k); if (rh != null) { Resource<?,?> res = rh.getResource(); root.add(new ResNode(name,status,k,res.reference)); } else { root.addChild(name,status,k); } } else { root.addChild(name,status,k); } } tree.setSelectionPath(new TreePath(root).pathByAddingChild(root.getChildAt(0))); } */ for (Class<? extends Resource<?,?>> k : Resource.kinds) { boolean hasNode = true; // Check to see if the appropriate node already exists. Enumeration<?> children = root.depthFirstEnumeration(); while (children.hasMoreElements()) { ResNode it = (ResNode) children.nextElement(); if (it.kind == k) { hasNode = false; break; } } if (!hasNode) continue; try { //NOTE: Use reflection on the class to see if it has a variable telling us whether to create //a node in the tree for the resource type. hasNode = k.getField("hasNode").getBoolean(hasNode); //$NON-NLS-1$ } catch (IllegalArgumentException e) { LGM.showDefaultExceptionHandler(e); } catch (NoSuchFieldException e) { LGM.showDefaultExceptionHandler(e); } catch (SecurityException e) { LGM.showDefaultExceptionHandler(e); } catch (IllegalAccessException e) { LGM.showDefaultExceptionHandler(e); } if (!hasNode) continue; String name = Resource.kindNamesPlural.get(k); byte status = InstantiableResource.class.isAssignableFrom(k) ? ResNode.STATUS_PRIMARY : ResNode.STATUS_SECONDARY; root.addChild(name,status,k); } tree.setSelectionPath(new TreePath(root).pathByAddingChild(root.getChildAt(0))); } /** * Commits all front-end changes to the back-end. * Usually do this in preparation for writing the back-end to some output stream, * such as a file (e.g. saving) or a plugin (e.g. compiling). * Notice that LGM actually traverses the tree committing *all* ResNodes with frames, * rather than just the open MDI frames. Since GameSettings and GameInfo do not have * ResourceFrames associated with them, we commit them separately. * @see LGM#commitAll() */ public static void commitAll() { Enumeration<?> nodes = LGM.root.preorderEnumeration(); while (nodes.hasMoreElements()) { ResNode node = (ResNode) nodes.nextElement(); if (node.frame != null) node.frame.commitChanges(); // update open frames } LGM.getExtensionPackages().commitChanges(); LGM.getConstantsFrame().commitChanges(); LGM.getGameInfo().commitChanges(); LGM.getGameSettings().commitChanges(); } public static void reload(boolean newRoot) { LGM.mdi.closeAll(); //TODO: Swing code must be executed on the Swing thread, //without delaying this passing a GMX to the main method //when launching will sometimes, about once every 5 loads, //throw an exception when setModel is called. //After several hours and days of testing I still haven't figured out why //the GMK reader doesn't have this but the GMX reader does, I believe because the GMX //reader uses more postponed references, but I can't figure out where or how it correlates. - Robert SwingUtilities.invokeLater(new Runnable() { @Override public void run() { InvisibleTreeModel ml = new InvisibleTreeModel(LGM.root); LGM.tree.setModel(ml); ml.activateFilter(pruneResultsCB.isSelected()); if (ml.isActivatedFilter()) { applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(),false,wholeWordCB.isSelected(),true); } LGM.tree.setSelectionRow(0); } }); // Reload the search tree so that orphaned references can be dumped. DefaultMutableTreeNode searchRoot = (DefaultMutableTreeNode) searchTree.getModel().getRoot(); searchRoot.removeAllChildren(); // Reload because root is invisible. ((DefaultTreeModel)searchTree.getModel()).reload(); LGM.eventSelect.reload(); ConfigurationManager.getInstance().setConfigList(LGM.currentFile.gameSettings); configsCombo.setModel(new DefaultComboBoxModel<GameSettings>(LGM.currentFile.gameSettings)); // NOTE: We do this to update the reference to the one now loaded // since we never close these frames, then we simply revert their controls. constantsFrame.res = LGM.currentFile.gameSettings.firstElement().constants; constantsFrame.resOriginal = LGM.currentFile.gameSettings.firstElement().constants.clone(); constantsFrame.revertResource(); constantsFrame.setVisible(false); gameInfo.res = LGM.currentFile.gameInfo; gameInfo.resOriginal = LGM.currentFile.gameInfo.clone(); gameInfo.revertResource(); gameInfo.setVisible(false); gameSet.res = LGM.currentFile.gameSettings.firstElement(); gameSet.resOriginal = LGM.currentFile.gameSettings.firstElement().clone(); gameSet.revertResource(); gameSet.setVisible(false); LGM.fireReloadPerformed(newRoot); } public static interface ReloadListener { /** * Called after LGM performs a reload, e.g. when a new file is created or loaded. * A reload causes the MDI to be flushed, the tree to refresh * (especially if a new root is provided), the EventPanel is recreated * (a hack to ensure that the events' link selectors know of the new root), * and Game Settings and Game Info are re-calibrated with their new settings * (but not recreated). Note that the Menu bar is left untouched and remains in tact. * @param newRoot indicates if a new root was provided to the tree * (e.g. the tree had to be re-populated) * @see LGM#reload(boolean newRoot) */ void reloadPerformed(boolean newRoot); } protected static ArrayList<ReloadListener> reloadListeners = new ArrayList<ReloadListener>(); static Action treeCopyAction = new AbstractAction("COPY") { //$NON-NLS-1$ /** * NOTE: Default UID generated, change if necessary. */ private static final long serialVersionUID = 2505969552404421504L; public void actionPerformed(ActionEvent ev) { Object obj = ev.getSource(); if (obj == null) return; JTree tree = null; if (!(obj instanceof JTree)) tree = LGM.searchTree; else tree = (JTree) obj; String text = ""; //$NON-NLS-1$ int[] rows = tree.getSelectionRows(); java.util.Arrays.sort(rows); for (int i = 0; i < rows.length; i++) { TreePath path = tree.getPathForRow(rows[i]); text += (i > 0 ? "\n" : "") + path.getLastPathComponent().toString().replaceAll("\\<[^>]*>",""); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$ } StringSelection selection = new StringSelection(text); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(selection, selection); } }; private static JTabbedPane treeTabs; public static void addReloadListener(ReloadListener l) { reloadListeners.add(l); } public static void removeReloadListener(ReloadListener l) { reloadListeners.remove(l); } protected static void fireReloadPerformed(boolean newRoot) { for (ReloadListener rl : reloadListeners) rl.reloadPerformed(newRoot); LGM.LOADING_PROJECT = false; } public static void addPluginResource(PluginResource pr) { ImageIcon i = pr.getIcon(); if (i != null) ResNode.ICON.put(pr.getKind(),i); String p = pr.getPrefix(); if (p != null) Prefs.prefixes.put(pr.getKind(),p); Resource.addKind(pr.getKind(),pr.getName3(),pr.getName(),pr.getPlural()); LGM.currentFile.resMap.put(pr.getKind(),pr.getResourceHolder()); ResourceFrame.factories.put(pr.getKind(),pr.getResourceFrameFactory()); } public static interface PluginResource { Class<? extends Resource<?,?>> getKind(); /** Can be null, in which case the default icon is used. */ ImageIcon getIcon(); String getName3(); String getName(); String getPlural(); String getPrefix(); ResourceHolder<?> getResourceHolder(); ResourceFrameFactory getResourceFrameFactory(); } public static abstract class SingletonPluginResource<T extends Resource<T,?>> implements PluginResource { public String getPlural() { return getName(); } public String getPrefix() { return null; } public ResourceHolder<?> getResourceHolder() { return new SingletonResourceHolder<T>(getInstance()); } public abstract T getInstance(); } public static class InvisibleTreeModel extends DefaultTreeModel { /** * */ private static final long serialVersionUID = 1L; protected boolean filterIsActive; public InvisibleTreeModel(TreeNode root) { this(root, false); } public InvisibleTreeModel(TreeNode root, boolean asksAllowsChildren) { this(root, false, false); } public InvisibleTreeModel(TreeNode root, boolean asksAllowsChildren, boolean filterIsActive) { super(root, asksAllowsChildren); this.filterIsActive = filterIsActive; } public void activateFilter(boolean newValue) { filterIsActive = newValue; } public boolean isActivatedFilter() { return filterIsActive; } public Object getChild(Object parent, int index) { if (filterIsActive) { if (parent instanceof ResNode) { return ((ResNode) parent).getChildAt(index, filterIsActive); } } return ((TreeNode) parent).getChildAt(index); } public int getChildCount(Object parent) { if (filterIsActive) { if (parent instanceof ResNode) { return ((ResNode) parent).getChildCount(filterIsActive); } } return ((TreeNode) parent).getChildCount(); } } private static boolean expressionMatch(String token, String expression, boolean matchCase, boolean wholeWord) { if (!matchCase) { token = token.toLowerCase(); expression = expression.toLowerCase(); } if (wholeWord) { return token.equals(expression); } else { //if (expression.length() == 0) { return false; } // without this all of your folders will be open by default, we don't want to // check matches with an empty string - don't touch this as everything works so just leave it here in case I come back to it - Robert B. Colton return token.contains(expression); } } public static DefaultMutableTreeNode applyFilterRecursion(Vector<ResNode> children, boolean filter, String expression, boolean matchCase, boolean wholeWord) { if (children == null) { return null; } DefaultMutableTreeNode firstResult = null; for (ResNode child : children) { boolean match = expressionMatch(child.toString(), expression, matchCase, wholeWord); if (firstResult == null && match) { firstResult = child; } DefaultMutableTreeNode childResult = applyFilterRecursion(child.getChildren(), filter, expression, matchCase, wholeWord) ; if (firstResult == null && childResult != null) { //if (childResult != null) { firstResult = childResult; } if (childResult != null || match) { child.setVisible(true); } else { child.setVisible(false); } } return firstResult; } public static boolean applyFilter(Vector<ResNode> children, boolean filter, String expression, boolean matchCase, boolean wholeWord, boolean selectFirst) { if (children == null) { return false; } DefaultMutableTreeNode firstResult = applyFilterRecursion(children, filter, expression, matchCase, wholeWord); if (firstResult != null && selectFirst) { tree.setSelectionPath(new TreePath(firstResult.getPath())); tree.updateUI(); return true; } tree.updateUI(); return false; } public static boolean searchFilter(ResNode child, String expression, boolean matchCase, boolean wholeWord, boolean backwards) { ResNode firstResult = null; while (child != null) { if (backwards) { child = (ResNode) child.getPreviousNode(); } else { child = (ResNode) child.getNextNode(); } if (child == null) break; boolean match = expressionMatch(child.toString(), expression, matchCase, wholeWord); if (firstResult == null && match) { firstResult = child; break; } } if (firstResult != null) { tree.setSelectionPath(new TreePath(firstResult.getPath())); tree.updateUI(); //tree.expandPath(new TreePath(firstResult.getPath())); return true; } return false; } public static class MatchBlock { public String content; public boolean highlighted; MatchBlock(String content, boolean highlighted) { this.content = content; this.highlighted = highlighted; } } public static class LineMatch { public int lineNum; public List<MatchBlock> matchedText = new ArrayList<MatchBlock>(); public String toHighlightableString() { boolean enablehtml = Prefs.highlightResultMatchBackground || Prefs.highlightResultMatchForeground; String text = (enablehtml ? "<html>" : "") + lineNum + ": "; for (MatchBlock block : matchedText) { if (block.highlighted && enablehtml) { text += "<span"; if (Prefs.highlightResultMatchBackground) { text += " bgcolor='" + Util.getHTMLColor(Prefs.resultMatchBackgroundColor,false) + "'"; } if (Prefs.highlightResultMatchForeground) { text += " color='" + Util.getHTMLColor(Prefs.resultMatchForegroundColor,false) + "'"; } text += ">"; } text += block.content; if (block.highlighted && enablehtml) { text += "</span>"; } } if (enablehtml) { text += "</html>"; } return text; } } private static String formatMatchCountText(String pretext, int matches) { boolean enablehtml = Prefs.highlightMatchCountBackground || Prefs.highlightMatchCountForeground; String text = (enablehtml ? "<html>" : "") + pretext + " " + (enablehtml ? "<font" : ""); if (Prefs.highlightMatchCountBackground) { text += " bgcolor='" + Util.getHTMLColor(Prefs.matchCountBackgroundColor,false) + "'"; } if (Prefs.highlightMatchCountForeground) { text += " color='" + Util.getHTMLColor(Prefs.matchCountForegroundColor,false) + "'"; } text += (enablehtml ? ">" : "") + "(" + matches + " " + Messages.getString("TreeFilter.MATCHES") + ")" + (enablehtml ? "</font></html>" : ""); return text; } private static final Pattern NEWLINE = Pattern.compile("\r\n|\r|\n"); static List<LineMatch> getMatchingLines(String code, Pattern content) { List<LineMatch> res = new ArrayList<LineMatch>(); Matcher m = content.matcher(code), nl = NEWLINE.matcher(code); // code editor starts at line 0 so we need to here as well int lineNum = 0, lineAt = 0, lastEnd = -1; LineMatch lastMatch = null; while (m.find()) { nl.region(lineAt, m.start()); int firstSkippedLineAt = lineAt; if (nl.find()) { firstSkippedLineAt = nl.start(); lineAt = nl.end(); ++lineNum; while (nl.find()) { ++lineNum; lineAt = nl.end(); } } if (lastMatch != null) { // We have to add the rest of the line to the old match, either way. // And if we're matching on the same line, we add that match, too. if (lineNum == lastMatch.lineNum) { lastMatch.matchedText.add(new MatchBlock(code.substring(lastEnd, m.start()), false)); lastMatch.matchedText.add(new MatchBlock(code.substring(m.start(), m.end()), true)); } else { lastMatch.matchedText.add( new MatchBlock(code.substring(lastEnd, firstSkippedLineAt), false)); } } if (lastMatch == null || lineNum != lastMatch.lineNum) { lastMatch = new LineMatch(); lastMatch.lineNum = lineNum; if (m.start() > lineAt) { lastMatch.matchedText.add(new MatchBlock(code.substring(lineAt, m.start()), false)); } lastMatch.matchedText.add(new MatchBlock(code.substring(m.start(), m.end()), true)); res.add(lastMatch); } lastEnd = m.end(); } if (lastMatch != null) { nl.region(lastEnd, code.length()); int indTo = (nl.find())? nl.start() : code.length(); lastMatch.matchedText.add(new MatchBlock(code.substring(lastEnd, indTo), false)); } return res; } /* public static void assertEquals(Object obj1, Object obj2) { if (obj1.equals(obj2)) { Debug.println("assertEquals: ",obj1.toString() + "," + obj2.toString()); } else { Debug.println("assertEquals: ","false"); } } public static void testThing() { String CODE = "runatestinatestwith\nsomemoretestsandthen\nyou'redone"; List<LineMatch> match = getMatchingLines(CODE, Pattern.compile("test")); LineMatch[] matches = (LineMatch[]) match.toArray(new LineMatch[match.size()]); assertEquals(2, matches.length); assertEquals(5, matches[0].matchedText.size()); assertEquals("runa", matches[0].matchedText.get(0).content); assertEquals("test", matches[0].matchedText.get(1).content); assertEquals("ina", matches[0].matchedText.get(2).content); assertEquals("test", matches[0].matchedText.get(3).content); assertEquals("with", matches[0].matchedText.get(4).content); assertEquals(3, matches[1].matchedText.size()); assertEquals("somemore", matches[1].matchedText.get(0).content); assertEquals("test", matches[1].matchedText.get(1).content); assertEquals("sandthen", matches[1].matchedText.get(2).content); } */ public static void buildSearchHierarchy(ResNode resNode, SearchResultNode resultRoot) { DefaultMutableTreeNode searchNode = (DefaultMutableTreeNode) LGM.searchTree.getModel().getRoot(); if (resNode == null) { searchNode.add(resultRoot); return; } TreeNode[] paths = resNode.getPath(); // start at 1 because we don't want to copy the root // subtract 1 so we don't consider the node itself for (int n = 1; n < paths.length - 1; n++) { ResNode pathNode = (ResNode) paths[n]; boolean found = false; for (int y = 0; y < searchNode.getChildCount(); y++) { DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) searchNode.getChildAt(y); if (childNode.getUserObject() == pathNode.getUserObject()) { searchNode = childNode; found = true; break; } } if (!found) { SearchResultNode newSearchNode = new SearchResultNode(pathNode.getUserObject()); newSearchNode.status = pathNode.status; searchNode.add(newSearchNode); searchNode = newSearchNode; } if (pathNode == resNode.getParent()) { searchNode.insert(resultRoot, searchNode.getChildCount() + resNode.getDepth()); } } } public static void searchInResourcesRecursion(DefaultMutableTreeNode node, Pattern pattern) { int numChildren = node.getChildCount(); for (int i = 0; i < numChildren; ++i) { DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i); if (child instanceof ResNode) { ResNode resNode = (ResNode)child; if (resNode.status != ResNode.STATUS_SECONDARY) { searchInResourcesRecursion(child, pattern); } else { SearchResultNode resultRoot = null; ResourceReference<?> ref = resNode.getRes(); if (ref != null) { Resource<?,?> resderef = ref.get(); if (resNode.frame != null) { resNode.frame.commitChanges(); resderef = resNode.frame.res; } if (resNode.kind == Script.class) { Script res = (Script) resderef; String code = res.getCode(); List<LineMatch> matches = getMatchingLines(code, pattern); if (matches.size() > 0) { resultRoot = new SearchResultNode(formatMatchCountText(res.getName(),matches.size())); resultRoot.ref = res.reference; resultRoot.status = ResNode.STATUS_SECONDARY; resultRoot.setIcon(res.getNode().getIcon()); for (LineMatch match : matches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = new SearchResultNode(text); resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.status = SearchResultNode.STATUS_RESULT; resultNode.data = new Object[]{match.lineNum}; resultRoot.add(resultNode); } } } } else if (resNode.kind == Shader.class) { Shader res = (Shader) resderef; String vcode = res.getVertexCode(); String fcode = res.getFragmentCode(); List<LineMatch> vertexmatches = getMatchingLines(vcode, pattern); List<LineMatch> fragmentmatches = getMatchingLines(fcode, pattern); if (vertexmatches.size() + fragmentmatches.size() > 0) { resultRoot = new SearchResultNode(formatMatchCountText(res.getName(),vertexmatches.size() + fragmentmatches.size())); resultRoot.ref = res.reference; resultRoot.status = ResNode.STATUS_SECONDARY; resultRoot.setIcon(res.getNode().getIcon()); SearchResultNode resultGroupNode = new SearchResultNode(formatMatchCountText( Messages.getString("TreeFilter.VERTEX_CODE") + ":",vertexmatches.size())); resultGroupNode.status = SearchResultNode.STATUS_VERTEX_CODE; resultRoot.add(resultGroupNode); for (LineMatch match : vertexmatches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = new SearchResultNode(text); resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.status = SearchResultNode.STATUS_RESULT; resultNode.data = new Object[]{match.lineNum}; resultGroupNode.add(resultNode); } } resultGroupNode = new SearchResultNode(formatMatchCountText( Messages.getString("TreeFilter.FRAGMENT_CODE") + ":",fragmentmatches.size())); resultGroupNode.status = SearchResultNode.STATUS_FRAGMENT_CODE; resultRoot.add(resultGroupNode); for (LineMatch match : fragmentmatches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = new SearchResultNode(text); resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.status = SearchResultNode.STATUS_RESULT; resultNode.data = new Object[]{match.lineNum}; resultGroupNode.add(resultNode); } } } } else if (resNode.kind == GmObject.class) { GmObject res = (GmObject) resderef; ArrayList<SearchResultNode> meNodes = new ArrayList<>(); int matchCount = 0; for (MainEvent me : res.mainEvents) { ArrayList<SearchResultNode> evNodes = new ArrayList<>(); int meMatches = 0; int mainid = 0; for (Event ev : me.events) { mainid = ev.mainId; ArrayList<SearchResultNode> actionNodes = new ArrayList<>(); int evMatches = 0; List<org.lateralgm.resources.sub.Action> actions = ev.actions; for (int ii = 0; ii < actions.size(); ii++) { org.lateralgm.resources.sub.Action act = actions.get(ii); ArrayList<SearchResultNode> resultNodes = new ArrayList<>(); int actMatches = 0; List<Argument> args = act.getArguments(); for (int iii = 0; iii < args.size(); iii++) { Argument arg = args.get(iii); String code = arg.getVal(); ResourceReference<? extends Resource<?,?>> aref = arg.getRes(); if (aref != null) { Resource<?,?> ares = aref.get(); code = ares.getName(); } List<LineMatch> matches = getMatchingLines(code, pattern); actMatches += matches.size(); for (LineMatch match : matches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = null; if (act.getLibAction().actionKind != org.lateralgm.resources.sub.Action.ACT_CODE) { boolean enablehtml = Prefs.highlightResultMatchBackground || Prefs.highlightResultMatchForeground; text = ((enablehtml ? "<html>" : "") + Integer.toString(iii) + text.substring(text.indexOf(":"),text.length())); resultNode = new SearchResultNode(text); resultNode.data = new Object[]{iii}; } else { resultNode = new SearchResultNode(text); resultNode.data = new Object[]{match.lineNum}; } resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.status = SearchResultNode.STATUS_RESULT; resultNodes.add(resultNode); } } } evMatches += actMatches; if (resultNodes.size() > 0) { // Uses the same method of getting the Action name as ActionFrame SearchResultNode actRoot = new SearchResultNode(formatMatchCountText(act.getLibAction().name.replace("_"," "),actMatches)); actRoot.status = SearchResultNode.STATUS_ACTION; actRoot.data = new Object[]{ii}; actRoot.setIcon(new ImageIcon(act.getLibAction().actImage.getScaledInstance(16,16,0))); for (SearchResultNode actn : resultNodes) { actRoot.add(actn); } actionNodes.add(actRoot); } } meMatches += evMatches; if (actionNodes.size() > 0) { SearchResultNode evRoot = new SearchResultNode(formatMatchCountText(ev.toString().replaceAll("<","<").replaceAll(">",">"), evMatches)); evRoot.status = SearchResultNode.STATUS_EVENT; evRoot.setIcon(LGM.getIconForKey("EventNode.EVENT" + ev.mainId)); evRoot.data = new Object[]{ev.mainId,ev.id}; for (SearchResultNode resn : actionNodes) { evRoot.add(resn); } evNodes.add(evRoot); } } matchCount += meMatches; if (evNodes.size() > 0) { if (evNodes.size() > 1) { SearchResultNode meRoot = new SearchResultNode(formatMatchCountText(Messages.getString("MainEvent.EVENT" + mainid),meMatches)); meRoot.status = SearchResultNode.STATUS_MAIN_EVENT; meRoot.setIcon(LGM.getIconForKey("EventNode.GROUP" + mainid)); for (SearchResultNode resn : evNodes) { meRoot.add(resn); } meNodes.add(meRoot); } else { for (SearchResultNode resn : evNodes) { meNodes.add(resn); } } } } if (meNodes.size() > 0) { resultRoot = new SearchResultNode(formatMatchCountText(res.getName(), matchCount)); resultRoot.ref = res.reference; resultRoot.status = ResNode.STATUS_SECONDARY; resultRoot.setIcon(res.getNode().getIcon()); } for (SearchResultNode resn : meNodes) { resultRoot.add(resn); } } else if (resNode.kind == Timeline.class) { Timeline res = (Timeline) resderef; ArrayList<SearchResultNode> momentNodes = new ArrayList<>(); int matchCount = 0; for (Moment mom : res.moments) { ArrayList<SearchResultNode> actionNodes = new ArrayList<>(); int momentMatches = 0; List<org.lateralgm.resources.sub.Action> actions = mom.actions; for (int ii = 0; ii < actions.size(); ii++) { org.lateralgm.resources.sub.Action act = actions.get(ii); ArrayList<SearchResultNode> resultNodes = new ArrayList<>(); int actMatches = 0; List<Argument> args = act.getArguments(); for (int iii = 0; iii < args.size(); iii++) { Argument arg = args.get(iii); String code = arg.getVal(); ResourceReference<? extends Resource<?,?>> aref = arg.getRes(); if (aref != null) { Resource<?,?> ares = aref.get(); code = ares.getName(); } List<LineMatch> matches = getMatchingLines(code, pattern); actMatches += matches.size(); for (LineMatch match : matches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = null; if (act.getLibAction().actionKind != org.lateralgm.resources.sub.Action.ACT_CODE) { boolean enablehtml = Prefs.highlightResultMatchBackground || Prefs.highlightResultMatchForeground; text = ((enablehtml ? "<html>" : "") + Integer.toString(iii) + text.substring(text.indexOf(":"),text.length())); resultNode = new SearchResultNode(text); resultNode.data = new Object[]{iii}; } else { resultNode = new SearchResultNode(text); resultNode.data = new Object[]{match.lineNum}; } resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.status = SearchResultNode.STATUS_RESULT; resultNodes.add(resultNode); } } } momentMatches += actMatches; if (resultNodes.size() > 0) { // Uses the same method of getting the Action name as ActionFrame SearchResultNode actRoot = new SearchResultNode(formatMatchCountText(act.getLibAction().name.replace("_"," "), actMatches)); actRoot.status = SearchResultNode.STATUS_ACTION; actRoot.data = new Object[]{ii}; actRoot.setIcon(new ImageIcon(act.getLibAction().actImage.getScaledInstance(16,16,0))); for (SearchResultNode actn : resultNodes) { actRoot.add(actn); } actionNodes.add(actRoot); } } matchCount += momentMatches; if (actionNodes.size() > 0) { SearchResultNode momentRoot = new SearchResultNode(formatMatchCountText(mom.toString(),momentMatches)); momentRoot.status = SearchResultNode.STATUS_MOMENT; momentRoot.data = new Object[]{mom.stepNo}; momentRoot.setIcon(null); for (SearchResultNode resn : actionNodes) { momentRoot.add(resn); } momentNodes.add(momentRoot); } } if (momentNodes.size() > 0) { resultRoot = new SearchResultNode(formatMatchCountText(res.getName(), matchCount)); resultRoot.ref = res.reference; resultRoot.status = ResNode.STATUS_SECONDARY; resultRoot.setIcon(res.getNode().getIcon()); } for (SearchResultNode momn : momentNodes) { resultRoot.add(momn); } } else if (resNode.kind == Room.class) { Room res = (Room) resderef; ArrayList<SearchResultNode> resultNodes = new ArrayList<>(); int matchCount = 0; String code = res.getCode(); List<LineMatch> matches = getMatchingLines(code, pattern); matchCount += matches.size(); ArrayList<SearchResultNode> codeNodes = new ArrayList<>(); for (LineMatch match : matches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = new SearchResultNode(text); resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.status = SearchResultNode.STATUS_RESULT; resultNode.data = new Object[]{match.lineNum}; codeNodes.add(resultNode); } } if (codeNodes.size() > 0) { SearchResultNode resultNode = new SearchResultNode(formatMatchCountText(Messages.getString("TreeFilter.CREATION_CODE"), matches.size())); resultNode.setIcon(null); resultNode.status = SearchResultNode.STATUS_ROOM_CREATION; resultNodes.add(resultNode); for (SearchResultNode codeNode : codeNodes) { resultNode.add(codeNode); } } for (Instance inst : res.instances) { code = inst.getCode(); matches = getMatchingLines(code, pattern); matchCount += matches.size(); codeNodes = new ArrayList<>(); for (LineMatch match : matches) { if (match.matchedText.size() > 0) { String text = match.toHighlightableString(); SearchResultNode resultNode = new SearchResultNode(text); resultNode.setIcon(LGM.getIconForKey("TreeFilter.RESULT")); resultNode.data = new Object[]{match.lineNum}; resultNode.status = SearchResultNode.STATUS_RESULT; codeNodes.add(resultNode); } } if (codeNodes.size() > 0) { SearchResultNode resultNode = new SearchResultNode(formatMatchCountText( Messages.getString("TreeFilter.INSTANCE") + " " + inst.getID(), matches.size())); Resource<?,?> obj = ((ResourceReference<?>)inst.properties.get(PInstance.OBJECT)).get(); resultNode.setIcon(obj == null ? null : obj.getNode().getIcon()); resultNode.status = SearchResultNode.STATUS_INSTANCE_CREATION; resultNode.data = new Object[]{inst.getID()}; resultNodes.add(resultNode); for (SearchResultNode codeNode : codeNodes) { resultNode.add(codeNode); } } } if (resultNodes.size() > 0) { resultRoot = new SearchResultNode(formatMatchCountText(res.getName(),matchCount)); resultRoot.ref = res.reference; resultRoot.status = ResNode.STATUS_SECONDARY; resultRoot.setIcon(res.getNode().getIcon()); } for (SearchResultNode resultNode : resultNodes) { resultRoot.add(resultNode); } } if (resultRoot != null) { TreeNode[] paths = resNode.getPath(); DefaultMutableTreeNode searchNode = (DefaultMutableTreeNode) LGM.searchTree.getModel().getRoot(); // start at 1 because we don't want to copy the root // subtract 1 so we don't consider the node itself for (int n = 1; n < paths.length - 1; n++) { ResNode pathNode = (ResNode) paths[n]; boolean found = false; for (int y = 0; y < searchNode.getChildCount(); y++) { DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) searchNode.getChildAt(y); if (childNode.getUserObject() == pathNode.getUserObject()) { searchNode = childNode; found = true; break; } } if (!found) { SearchResultNode newSearchNode = new SearchResultNode(pathNode.getUserObject()); newSearchNode.status = pathNode.status; searchNode.add(newSearchNode); searchNode = newSearchNode; } if (pathNode == resNode.getParent()) { searchNode.add(resultRoot); } } } } } } } } public static void searchInResources(DefaultMutableTreeNode node, String expression, boolean regex, boolean matchCase, boolean wholeWord) { DefaultMutableTreeNode searchRoot = (DefaultMutableTreeNode) searchTree.getModel().getRoot(); searchRoot.removeAllChildren(); Pattern pattern = Pattern.compile(wholeWord? "\\b" + Pattern.quote(expression) + "\\b" : regex? expression : Pattern.quote(expression), matchCase? 0 : Pattern.CASE_INSENSITIVE); searchInResourcesRecursion(node, pattern); // Reload because root is invisible. ((DefaultTreeModel)searchTree.getModel()).reload(); } static class SearchResultNode extends DefaultMutableTreeNode { /** * NOTE: Default UID generated, change if necessary. */ private static final long serialVersionUID = -8827316922729826331L; public static final byte STATUS_RESULT = 4; public static final byte STATUS_MAIN_EVENT = 5; public static final byte STATUS_EVENT = 6; public static final byte STATUS_MOMENT = 7; public static final byte STATUS_VERTEX_CODE = 8; public static final byte STATUS_FRAGMENT_CODE = 9; public static final byte STATUS_ACTION = 10; public static final byte STATUS_ROOM_CREATION = 11; public static final byte STATUS_INSTANCE_CREATION = 12; public byte status; ResourceReference<?> ref; private Icon icon = null; Object[] data; Object[] parentdata; public SearchResultNode() { super(); } public SearchResultNode(Object text) { super(text); } public void setIcon(Icon ico) { icon = ico; } public ResourceReference<?> getRef() { SearchResultNode par = (SearchResultNode) this.getParent(); ResourceReference<?> ret = par.ref; while (ret == null) { par = (SearchResultNode) par.getParent(); ret = par.ref; } return ret; } public void threadCaretUpdate(final CodeTextArea code, int row, int col) { SwingUtilities.invokeLater(new Runnable(){ @Override public void run() { code.setCaretPosition((Integer)data[0],0); code.text.centerCaret(); code.repaint(); } }); } public void openFrame() { if (status >= STATUS_RESULT) { DefaultMutableTreeNode node = (DefaultMutableTreeNode) this.getParent(); if (node instanceof SearchResultNode) { SearchResultNode parNode = (SearchResultNode) node; parNode.openFrame(); ResourceReference<?> parentRef = this.getRef(); if (parentRef != null) { Resource<?,?> res = parentRef.get(); if (res != null) { ResNode resNode = res.getNode(); if (resNode != null) { ResourceFrame<?,?> frame = resNode.frame; if (frame != null) { if (resNode.kind == GmObject.class) { GmObjectFrame objframe = (GmObjectFrame) frame; if (status == STATUS_EVENT) { objframe.setSelectedEvent((Integer)data[0],(Integer)data[1]); } else if (status == STATUS_ACTION) { //TODO: There is a bug here where if the user deletes the action //and then selects it again from the search results tree it may open //and in fact scope a different action in the list because we have //no unique reference to the action we want to open, and we can't //because the frame may have been closed. May possibly lead to an NPE objframe.actions.setSelectedIndex((Integer)data[0]); org.lateralgm.resources.sub.Action act = objframe.actions.getSelectedValue(); if (act != null) { ActionFrame af = (ActionFrame) ActionList.openActionFrame(objframe,act); parentdata = new Object[]{af}; } } else if (status == STATUS_RESULT && parNode.status == STATUS_ACTION) { ActionFrame af = (ActionFrame) parNode.parentdata[0]; org.lateralgm.resources.sub.Action act = af.getAction(); if (act.getLibAction().actionKind != org.lateralgm.resources.sub.Action.ACT_CODE) { af.focusArgumentComponent((Integer)data[0]); } else { if ((Integer)data[0] < af.code.getLineCount()) { threadCaretUpdate(af.code, (Integer)data[0],0); } } } } else if (resNode.kind == Timeline.class) { TimelineFrame tmlframe = (TimelineFrame) frame; if (status == STATUS_MOMENT) { tmlframe.setSelectedMoment((Integer)data[0]); } else if (status == STATUS_ACTION) { tmlframe.actions.setSelectedIndex((Integer)data[0]); org.lateralgm.resources.sub.Action act = tmlframe.actions.getSelectedValue(); if (act != null) { ActionFrame af = (ActionFrame) ActionList.openActionFrame(tmlframe,act); parentdata = new Object[]{af}; } } else if (status == STATUS_RESULT && parNode.status == STATUS_ACTION) { ActionFrame af = (ActionFrame) parNode.parentdata[0]; org.lateralgm.resources.sub.Action act = af.getAction(); if (act.getLibAction().actionKind != org.lateralgm.resources.sub.Action.ACT_CODE) { af.focusArgumentComponent((Integer)data[0]); } else { if ((Integer)data[0] < af.code.getLineCount()) { threadCaretUpdate(af.code, (Integer)data[0],0); } } } } else if (resNode.kind == Room.class) { RoomFrame roomframe = (RoomFrame) frame; if (status == STATUS_INSTANCE_CREATION) { roomframe.tabs.setSelectedIndex(0); parentdata = new Object[]{roomframe.openInstanceCodeFrame((Integer)data[0], true)}; } else if (status == STATUS_ROOM_CREATION) { roomframe.tabs.setSelectedIndex(1); parentdata = new Object[]{roomframe.openRoomCreationCode()}; } else if (status == STATUS_RESULT) { if (parNode.status == STATUS_INSTANCE_CREATION || parNode.status == STATUS_ROOM_CREATION) { CodeFrame cf = (CodeFrame) parNode.parentdata[0]; if ((Integer)data[0] < cf.code.getLineCount()) { threadCaretUpdate(cf.code, (Integer)data[0],0); } } } } else if (resNode.kind == Script.class) { final ScriptFrame scrframe = (ScriptFrame) frame; if (status == STATUS_RESULT) { if ((Integer)data[0] < scrframe.code.text.getLineCount()) { threadCaretUpdate(scrframe.code, (Integer)data[0],0); } } } else if (resNode.kind == Shader.class) { ShaderFrame shrframe = (ShaderFrame) frame; if (status == STATUS_VERTEX_CODE) { shrframe.editors.setSelectedIndex(0); } else if (status == STATUS_FRAGMENT_CODE) { shrframe.editors.setSelectedIndex(1); } else if (status == STATUS_RESULT) { if (parNode.status == STATUS_VERTEX_CODE) { shrframe.vcode.requestFocusInWindow(); if ((Integer)data[0] < shrframe.vcode.text.getLineCount()) { threadCaretUpdate(shrframe.vcode, (Integer)data[0],0); } } else if (parNode.status == STATUS_FRAGMENT_CODE) { shrframe.fcode.requestFocusInWindow(); if ((Integer)data[0] < shrframe.fcode.text.getLineCount()) { threadCaretUpdate(shrframe.fcode, (Integer)data[0],0); } } } } } } } } } } else if (status == ResNode.STATUS_SECONDARY) { if (ref != null) { Resource<?,?> res = ref.get(); if (res != null) { ResNode node = res.getNode(); if (node != null) { node.openFrame(); } } } } } } public static class SearchResultsRenderer extends DefaultTreeCellRenderer { SearchResultNode last; private Color nonSelectColor; private static final long serialVersionUID = 1L; public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (value instanceof SearchResultNode) { last = (SearchResultNode) value; } // this is a patch for the DarkEye Synthetica look and feel which for some reason // overrides its own UI property in its paint method, likely a bug on their part // same fix applied in GmTreeGraphics.java setTextNonSelectionColor(nonSelectColor); Component com = super.getTreeCellRendererComponent(tree,value,sel,expanded,leaf,row,hasFocus); // Bold primary nodes if (value instanceof SearchResultNode && com instanceof JLabel) { SearchResultNode rn = (SearchResultNode) value; JLabel label = (JLabel) com; if (rn.status == ResNode.STATUS_PRIMARY) { label.setText("<html><b>" + label.getText() + "</b></html>"); } } return com; } @Override public void updateUI() { super.updateUI(); nonSelectColor = this.getTextNonSelectionColor(); } public Icon getLeafIcon() { if (last != null) { Icon icon = last.icon; if (icon != null) return icon; } return null; } public Icon getClosedIcon() { if (last != null) { if (last.status == ResNode.STATUS_PRIMARY || last.status == ResNode.STATUS_GROUP) { return LGM.getIconForKey("GmTreeGraphics.GROUP"); } else { Icon icon = last.icon; if (icon != null) return icon; } } return null; } public Icon getOpenIcon() { if (last != null) { if (last.status == ResNode.STATUS_PRIMARY || last.status == ResNode.STATUS_GROUP) { return LGM.getIconForKey("GmTreeGraphics.GROUP_OPEN"); } else { Icon icon = last.icon; if (icon != null) return icon; } } return null; } } public static void applyPreferences() { if (javaVersion >= 10700 && !Prefs.locale.toLanguageTag().equals("und")) { Locale.setDefault(Prefs.locale); } if (Prefs.direct3DAcceleration.equals("off")) { //$NON-NLS-1$ //java6u10 regression causes graphical xor to be very slow System.setProperty("sun.java2d.d3d","false"); //$NON-NLS-1$ //$NON-NLS-2$ System.setProperty("sun.java2d.ddscale", "false"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (Prefs.direct3DAcceleration.equals("on")) { //$NON-NLS-1$ System.setProperty("sun.java2d.d3d","true"); //$NON-NLS-1$ //$NON-NLS-2$ System.setProperty("sun.java2d.ddscale", "true"); //$NON-NLS-1$ //$NON-NLS-2$ } if (Prefs.openGLAcceleration.equals("off")) { //$NON-NLS-1$ System.setProperty("sun.java2d.opengl","false"); //$NON-NLS-1$ //$NON-NLS-2$ } else if (Prefs.openGLAcceleration.equals("on")) { //$NON-NLS-1$ System.setProperty("sun.java2d.opengl","true"); //$NON-NLS-1$ //$NON-NLS-2$ //TODO: Causes JFrame's other than the main JFrame to be white on Windows 8 //under all Look and Feels with AMD graphics card, seems to be a known issue //with OpenGL for Java on Windows, as usual Oracle is unlikely to fix it. //https://community.oracle.com/thread/1263741?start=0&tstart=0 if (System.getProperty("os.name").toLowerCase().startsWith("windows")) { //$NON-NLS-1$ //$NON-NLS-2$ //force directx completely off System.setProperty("sun.java2d.noddraw","true"); //$NON-NLS-1$ //$NON-NLS-2$ System.setProperty("sun.java2d.opengl.fbobject","false"); //$NON-NLS-1$ //$NON-NLS-2$ } } if (!Prefs.antialiasControlFont.equals("default")) { //$NON-NLS-1$ // Set antialiasing mode System.setProperty("awt.useSystemAAFontSettings",Prefs.antialiasControlFont); //$NON-NLS-1$ // if the other antialiasing option is not off then assume this one is on as well if (Prefs.antialiasControlFont.equals("off")) { //$NON-NLS-1$ System.setProperty("swing.aatext","false"); //$NON-NLS-1$ //$NON-NLS-2$ } else { System.setProperty("swing.aatext","true"); //$NON-NLS-1$ //$NON-NLS-2$ } } // this is necessary to make sure open/save dialogs get fixed JFrame.setDefaultLookAndFeelDecorated(Prefs.decorateWindowBorders); JDialog.setDefaultLookAndFeelDecorated(Prefs.decorateWindowBorders); Window[] windows = Window.getWindows(); LookAndFeel laf = UIManager.getLookAndFeel(); for (Window window : windows) { final boolean visible = window.isVisible(); final boolean decorate = Prefs.decorateWindowBorders && laf.getSupportsWindowDecorations(); if (window instanceof Frame) { final Frame decframe = (Frame) window; if (decorate != decframe.isUndecorated()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { decframe.dispose(); decframe.setShape(null); decframe.setUndecorated(decorate); if (decframe instanceof RootPaneContainer) ((RootPaneContainer) decframe).getRootPane().setWindowDecorationStyle( decorate ? JRootPane.FRAME : JRootPane.NONE); decframe.setVisible(visible); } }); } } else if (window instanceof Dialog) { final Dialog decdialog = (Dialog) window; if (decorate != decdialog.isUndecorated()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { decdialog.dispose(); decdialog.setShape(null); decdialog.setUndecorated(decorate); if (decdialog instanceof RootPaneContainer) ((RootPaneContainer) decdialog).getRootPane().setWindowDecorationStyle( decorate ? JRootPane.PLAIN_DIALOG : JRootPane.NONE); decdialog.setVisible(visible); } }); } } } } public static void main(final String[] args) throws InvocationTargetException, InterruptedException { // Set the default uncaught exception handler. LGM.setDefaultExceptionHandler(); LGM.applyPreferences(); Messages.updateLangPack(); //TODO: Should probably make these preferences as well, but I don't have a Mac to test - Robert //Put the Mac menu bar where it belongs (ignored by other systems) System.setProperty("apple.laf.useScreenMenuBar","true"); //$NON-NLS-1$ //$NON-NLS-2$ //Set the Mac menu bar title to the correct name (also adds a useless About entry, so disabled) //System.setProperty("com.apple.mrj.application.apple.menu.about.name",Messages.getString("LGM.NAME")); //$NON-NLS-1$ //$NON-NLS-2$ System.out.format("Java Version: %d (%s)\n",javaVersion,System.getProperty("java.version")); //$NON-NLS-1$ if (javaVersion < 10700) System.out.println("Some program functionality will be limited due to your outdated Java version"); //$NON-NLS-1$ // Load external look and feels the user has plugged in loadLookAndFeels(); iconspack = Prefs.iconPack; setLookAndFeel(Prefs.swingTheme); themechanged = false; // must be called after setting the look and feel JFrame.setDefaultLookAndFeelDecorated(Prefs.decorateWindowBorders); JDialog.setDefaultLookAndFeelDecorated(Prefs.decorateWindowBorders); SplashProgress splashProgress = new SplashProgress(); splashProgress.start(); //Set up temp dir and work dir Util.tweakIIORegistry(); tempDir = new File(System.getProperty("java.io.tmpdir"),"lgm"); //$NON-NLS-1$ //$NON-NLS-2$ if (!tempDir.exists()) { tempDir.mkdir(); if (javaVersion >= 10600) { tempDir.setReadable(true,false); tempDir.setWritable(true,false); } } splashProgress.progress(10,Messages.getString("LGM.SPLASH_LANG")); //$NON-NLS-1$ Messages.updateLangPack(); splashProgress.progress(15,Messages.getString("LGM.SPLASH_CURSOR")); //$NON-NLS-1$ createMouseCursors(); splashProgress.progress(20,Messages.getString("LGM.SPLASH_LIBS")); //$NON-NLS-1$ LibManager.autoLoad(); splashProgress.progress(30,Messages.getString("LGM.SPLASH_TOOLS")); //$NON-NLS-1$ JToolBar toolbar = createToolBar(); treeTabs = new JTabbedPane(); tree = createTree(); DefaultMutableTreeNode sroot = new DefaultMutableTreeNode("root"); //$NON-NLS-1$ searchTree = new JTree(sroot); // Create tree context menu final JPopupMenu searchMenu = new JPopupMenu(); JMenuItem expandAllItem = new JMenuItem(Messages.getString("TreeFilter.EXPANDALL")); expandAllItem.setIcon(LGM.getIconForKey("TreeFilter.EXPANDALL")); expandAllItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("TreeFilter.EXPANDALL"))); expandAllItem.addActionListener(new ActionListener() { public void expandChildren(JTree tree, DefaultMutableTreeNode node) { Enumeration<?> children = node.children(); DefaultMutableTreeNode it = null; while (children.hasMoreElements()) { it = (DefaultMutableTreeNode) children.nextElement(); tree.expandPath(new TreePath(it.getPath())); if (it.getChildCount() > 0) { expandChildren(tree, it); } } } public void actionPerformed(ActionEvent ev) { expandChildren(LGM.searchTree,(DefaultMutableTreeNode) LGM.searchTree.getModel().getRoot()); } }); searchMenu.add(expandAllItem); JMenuItem collapseAllItem = new JMenuItem(Messages.getString("TreeFilter.COLLAPSEALL")); collapseAllItem.setIcon(LGM.getIconForKey("TreeFilter.COLLAPSEALL")); collapseAllItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("TreeFilter.COLLAPSEALL"))); collapseAllItem.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ev) { //NOTE: The code for expanding all nodes does not work here because collapsing a child node //will expand its parent, so you have to do it in reverse. For now I will just reload the tree. ((DefaultTreeModel)searchTree.getModel()).reload(); } }); searchMenu.add(collapseAllItem); searchMenu.addSeparator(); JMenuItem copyItem = new JMenuItem(); copyItem.setAction(treeCopyAction); copyItem.setText(Messages.getString("TreeFilter.COPY")); copyItem.setIcon(LGM.getIconForKey("TreeFilter.COPY")); copyItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("TreeFilter.COPY"))); searchTree.getActionMap().put("COPY", treeCopyAction); searchTree.getInputMap().put(copyItem.getAccelerator(), "COPY"); // Add it to the main tree as well to remove HTML formatting tree.getActionMap().put("COPY", treeCopyAction); tree.getInputMap().put(copyItem.getAccelerator(), "COPY"); searchMenu.add(copyItem); searchMenu.addSeparator(); JMenuItem selectAllItem = new JMenuItem(Messages.getString("TreeFilter.SELECTALL")); selectAllItem.setIcon(LGM.getIconForKey("TreeFilter.SELECTALL")); selectAllItem.setAccelerator(KeyStroke.getKeyStroke(Messages.getKeyboardString("TreeFilter.SELECTALL"))); //NOTE: It's possible to grab the trees built in Select All action. //selectAllItem.setAction(searchTree.getActionMap().get(searchTree.getInputMap().get(selectAllItem.getAccelerator()))); selectAllItem.addActionListener(new ActionListener() { public void selectAllChildren(JTree tree, DefaultMutableTreeNode node) { Enumeration<?> children = node.children(); DefaultMutableTreeNode it = null; while (children.hasMoreElements()) { it = (DefaultMutableTreeNode) children.nextElement(); tree.addSelectionPath(new TreePath(it.getPath())); if (tree.isExpanded(new TreePath(it.getPath()))) { selectAllChildren(tree, it); } } } public void actionPerformed(ActionEvent ev) { selectAllChildren(LGM.searchTree,(DefaultMutableTreeNode) LGM.searchTree.getModel().getRoot()); } }); searchMenu.add(selectAllItem); searchTree.setToggleClickCount(0); // we only want to expand on double click with group nodes, not result nodes searchTree.setCellRenderer(new SearchResultsRenderer()); searchTree.setRootVisible(false); searchTree.setShowsRootHandles(true); searchTree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION); searchTree.addMouseListener(new MouseAdapter() { public void mouseReleased(MouseEvent me) { TreePath path = LGM.searchTree.getPathForLocation(me.getX(),me.getY()); boolean inpath = false; if (path != null) { //Check to see if we have clicked on a different node then the one //currently selected. TreePath[] paths = LGM.searchTree.getSelectionPaths(); if (paths != null) { for (int i = 0; i < paths.length; i++) { if (paths[i].equals(path)) { inpath = true; } } } if (me.getModifiers() == InputEvent.BUTTON1_MASK && inpath) { LGM.searchTree.setSelectionPath(path); } } //Isn't Java supposed to handle ctrl+click for us? For some reason it doesn't. if (me.getModifiers() == InputEvent.BUTTON3_MASK && me.getClickCount() == 1) { // Yes the right click button does change the selection, // go ahead and experiment with Eclipse, CodeBlocks, Visual Studio // or Qt. Swing's default component popup listener does not do this // indicating it is an inconsistency with the framework compared to // other GUI libraries. if (!inpath && path != null) { LGM.searchTree.setSelectionPath(path); } searchMenu.show((Component)me.getSource(), me.getX(), me.getY()); return; } if (path == null) return; DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent(); if (node == null) return; if (me.getModifiers() == InputEvent.BUTTON1_MASK && me.getClickCount() >= 2 && ((me.getClickCount() & 1) == 0)) { if (node instanceof SearchResultNode) { SearchResultNode srn = (SearchResultNode) node; if (srn.status >= ResNode.STATUS_SECONDARY) { srn.openFrame(); return; } else { if (LGM.searchTree.isExpanded(path)) { LGM.searchTree.collapsePath(path); } else { LGM.searchTree.expandPath(path); } } } else { if (LGM.searchTree.isExpanded(path)) { LGM.searchTree.collapsePath(path); } else { LGM.searchTree.expandPath(path); } } } } }); contents = new JPanel(new BorderLayout()); contents.add(BorderLayout.CENTER,createMDI()); eventSelect = new EventPanel(); // could possibly be used to force the toolbar with event panel to popout // reducing code, i can not get it to work right however //((BasicToolBarUI) eventSelect.getUI()).setFloating(true, new Point(500,50)); splashProgress.progress(40,Messages.getString("LGM.SPLASH_THREAD")); //$NON-NLS-1$ constantsFrame = new ConstantsFrame(currentFile.defaultConstants); mdi.add(constantsFrame); gameInfo = new GameInformationFrame(currentFile.gameInfo); mdi.add(gameInfo); gameSet = new GameSettingFrame(currentFile.gameSettings.firstElement()); mdi.add(gameSet); extSet = new ExtensionPackagesFrame(currentFile.extPackages); mdi.add(extSet); splashProgress.progress(50,Messages.getString("LGM.SPLASH_MENU")); //$NON-NLS-1$ frame = new JFrame(Messages.format("LGM.TITLE", //$NON-NLS-1$ Messages.getString("LGM.NEWGAME"))); //$NON-NLS-1$ menuBar = new GmMenuBar(); frame.setJMenuBar(menuBar); splashProgress.progress(60,Messages.getString("LGM.SPLASH_UI")); //$NON-NLS-1$ JPanel f = new JPanel(new BorderLayout()); frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); frame.addWindowListener(new java.awt.event.WindowAdapter() { public void windowClosing(WindowEvent winEvt) { LGM.onMainFrameClosed(); } }); final JFrame filterSettingsFrame = createFilterSettingsFrame(); filterText = new HintTextField(Messages.getString("TreeFilter.SEARCHFOR"),true); JButton prevButton = new JButton(LGM.getIconForKey("TreeFilter.PREV")); prevButton.setToolTipText(Messages.getString("TreeFilter.PREV")); prevButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { searchFilter((ResNode)tree.getLastSelectedPathComponent(),filterText.getText(), matchCaseCB.isSelected(), wholeWordCB.isSelected(), true); } }); JButton nextButton = new JButton(LGM.getIconForKey("TreeFilter.NEXT")); nextButton.setToolTipText(Messages.getString("TreeFilter.NEXT")); nextButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { searchFilter((ResNode)tree.getLastSelectedPathComponent(), filterText.getText(), matchCaseCB.isSelected(), wholeWordCB.isSelected(), false); } }); JButton searchInButton = new JButton(LGM.getIconForKey("TreeFilter.SEARCHIN")); searchInButton.setToolTipText(Messages.getString("TreeFilter.SEARCHIN")); searchInButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { if (filterText.getText().length() <= 0) return; InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); searchInResources((DefaultMutableTreeNode) ml.getRoot(), filterText.getText(), regexCB.isSelected(), matchCaseCB.isSelected(), wholeWordCB.isSelected()); setSelectedTab(treeTabs, Messages.getString("TreeFilter.TAB_SEARCHRESULTS")); } }); JButton setButton = new JButton(LGM.getIconForKey("TreeFilter.SET")); setButton.setToolTipText(Messages.getString("TreeFilter.SET")); setButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { filterSettingsFrame.setVisible(true); } }); filterText.getDocument().addDocumentListener(new DocumentListener() { public void changedUpdate(DocumentEvent e) { } public void removeUpdate(DocumentEvent e) { InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); if (ml.isActivatedFilter()) { applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(),false,wholeWordCB.isSelected(),true); } else { searchFilter(root, filterText.getText(), matchCaseCB.isSelected(), wholeWordCB.isSelected(), false); } } public void insertUpdate(DocumentEvent e) { InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); if (ml.isActivatedFilter()) { applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(),false,wholeWordCB.isSelected(),true); } else { searchFilter(root, filterText.getText(), matchCaseCB.isSelected(), wholeWordCB.isSelected(), false); } } }); filterText.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { if (filterText.getText().length() <= 0) return; InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); searchInResources((DefaultMutableTreeNode) ml.getRoot(), filterText.getText(), regexCB.isSelected(), matchCaseCB.isSelected(), wholeWordCB.isSelected()); setSelectedTab(treeTabs, Messages.getString("TreeFilter.TAB_SEARCHRESULTS")); } }); // Use a toolbar so that the buttons render like tool buttons and smaller. filterPanel = new CustomJToolBar(); // Use a custom layout so that the filterText control will stretch horizontally under // all Look and Feels. GroupLayout filterLayout = new GroupLayout(filterPanel); filterLayout.setHorizontalGroup(filterLayout.createSequentialGroup() /**/.addComponent(filterText) /**/.addComponent(prevButton) /**/.addComponent(nextButton) /**/.addComponent(searchInButton) /**/.addComponent(setButton)); filterLayout.setVerticalGroup(filterLayout.createParallelGroup(Alignment.CENTER) /**/.addComponent(filterText, PREFERRED_SIZE, PREFERRED_SIZE, PREFERRED_SIZE) /**/.addComponent(prevButton) /**/.addComponent(nextButton) /**/.addComponent(searchInButton) /**/.addComponent(setButton)); filterPanel.setLayout(filterLayout); filterPanel.setFloatable(true); filterPanel.setVisible(Prefs.showTreeFilter); treeTabs.addTab(Messages.getString("TreeFilter.TAB_RESOURCES"),new JScrollPane(tree)); treeTabs.addTab(Messages.getString("TreeFilter.TAB_SEARCHRESULTS"),new JScrollPane(searchTree)); if (Prefs.dockEventPanel) { treeTabs.addTab(Messages.getString("TreeFilter.TAB_EVENTS"),eventSelect); } else { eventSelect.setVisible(false); // must occur after adding split } JPanel hierarchyPanel = new JPanel(); hierarchyPanel.setLayout(new BorderLayout(0, 0)); hierarchyPanel.add(filterPanel, BorderLayout.NORTH); hierarchyPanel.add(treeTabs,BorderLayout.CENTER); hierarchyPanel.setPreferredSize(new Dimension(320, 320)); //OutputManager.initialize(); JSplitPane verSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT,true,contents,OutputManager.outputTabs); final JSplitPane horSplit; if (Prefs.rightOrientation) { horSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true,verSplit,hierarchyPanel); horSplit.setResizeWeight(1d); } else { horSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,true,hierarchyPanel,verSplit); } f.add(horSplit); frame.setContentPane(f); frame.setTransferHandler(Listener.getInstance().fc.new LGMDropHandler()); f.add(BorderLayout.NORTH,toolbar); f.setOpaque(true); splashProgress.progress(65,Messages.getString("LGM.SPLASH_LOGO")); //$NON-NLS-1$ try { ICOFile icoFile = new ICOFile(LGM.class.getClassLoader().getResource( "org/lateralgm/main/lgm-logo.ico")); //$NON-NLS-1$ frame.setIconImages(icoFile.getImages()); } catch (Exception e) { LGM.showDefaultExceptionHandler(e); } // let the user specify their own background if (new File("lookandfeels/lgmbackground.png").exists()) { applyBackground("lookandfeels/lgmbackground.png"); //$NON-NLS-1$ } else { applyBackground("org/lateralgm/main/lgmbackground.png"); //$NON-NLS-1$ } splashProgress.progress(70,Messages.getString("LGM.SPLASH_TREE")); //$NON-NLS-1$ populateTree(); splashProgress.progress(80,Messages.getString("LGM.SPLASH_PLUGINS")); //$NON-NLS-1$ LOADING_PROJECT = true; loadPlugins(); splashProgress.complete(); // this is necessary for the next call to properly center the frame or // the method won't have the width/height of the window and instead the // top-left corner of the window will be in the exact center of the screen frame.pack(); // this makes sure the first time default location of the window that has // not been stored is centered to the screen frame.setLocationRelativeTo(null); // call this after packing the frame and setting its default location new FramePrefsHandler(frame); // finally, set the frame visible frame.setVisible(true); // Load any projects entered on the command line if (args.length > 0 && args[0].length() > 0) { Listener.getInstance().fc.open(new File(args[0])); } else { LOADING_PROJECT = false; } } private static JFrame createFilterSettingsFrame() { final JFrame filterSettings = new JFrame(); filterSettings.setIconImage(LGM.getIconForKey("TreeFilter.ICON").getImage()); filterSettings.setTitle(Messages.getString("TreeFilter.TITLE")); filterSettings.setResizable(false); wholeWordCB = new JCheckBox(Messages.getString("TreeFilter.WHOLEWORD")); wholeWordCB.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(), matchCaseCB.isSelected(),wholeWordCB.isSelected(),false); } }); regexCB = new JCheckBox(Messages.getString("TreeFilter.REGEX")); regexCB.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(), matchCaseCB.isSelected(),wholeWordCB.isSelected(),false); } }); matchCaseCB = new JCheckBox(Messages.getString("TreeFilter.MATCHCASE")); matchCaseCB.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(), matchCaseCB.isSelected(),wholeWordCB.isSelected(),false); } }); pruneResultsCB = new JCheckBox(Messages.getString("TreeFilter.PRUNERESULTS")); pruneResultsCB.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { InvisibleTreeModel ml = (InvisibleTreeModel) LGM.tree.getModel(); ml.activateFilter(pruneResultsCB.isSelected()); applyFilter(root.getChildren(),ml.isActivatedFilter(),filterText.getText(),false, wholeWordCB.isSelected(),false); } }); closeButton = new JButton(Messages.getString("TreeFilter.CLOSE")); closeButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent arg0) { filterSettings.setVisible(false); } }); JPanel panel = new JPanel(); GroupLayout gl = new GroupLayout(panel); gl.setAutoCreateGaps(true); gl.setAutoCreateContainerGaps(true); panel.setLayout(gl); filterSettings.getContentPane().setLayout(new GridBagLayout()); filterSettings.add(panel); gl.setHorizontalGroup(gl.createParallelGroup(GroupLayout.Alignment.CENTER) /**/.addGroup(gl.createSequentialGroup() /* */.addGroup(gl.createParallelGroup() /* */.addComponent(wholeWordCB) /* */.addComponent(matchCaseCB)) /* */.addGroup(gl.createParallelGroup() /* */.addComponent(regexCB) /* */.addComponent(pruneResultsCB))) /**/.addComponent(closeButton)); gl.setVerticalGroup(gl.createSequentialGroup() /**/.addGroup(gl.createParallelGroup() /* */.addGroup(gl.createSequentialGroup() /* */.addComponent(wholeWordCB) /* */.addComponent(matchCaseCB)) /* */.addGroup(gl.createSequentialGroup() /* */.addComponent(regexCB) /* */.addComponent(pruneResultsCB))) /**/.addComponent(closeButton)); filterSettings.pack(); filterSettings.setSize(280, 140); filterSettings.setLocationRelativeTo(LGM.frame); return filterSettings; } public static int getTabIndex(JTabbedPane tabs, String title) { for (int i = 0; i < tabs.getTabCount(); i++) { if (tabs.getTitleAt(i).equals(title)) return i; } return -1; } public static void setSelectedTab(JTabbedPane tabs, String title) { if (tabs.getTitleAt(tabs.getSelectedIndex()).equals(title)) return; int index = getTabIndex(tabs, title); if (index == -1) return; tabs.setSelectedIndex(index); } /* * TODO: This checks for changes by iterating the tree, but there is one small caveat * because of the todo comment above this will not work because the top level nodes * for non-instantiable resources also have a null reference. The solution for now * to also make ENIGMA settings work was to iterate the resmap instead. */ public static boolean checkForChangesInTree(DefaultMutableTreeNode node) { Enumeration<?> e = node.children(); while (e.hasMoreElements()){ ResNode rnode = (ResNode) e.nextElement(); if (rnode.status != ResNode.STATUS_SECONDARY) { if (checkForChangesInTree(rnode)) return true; } if (rnode.newRes) { return true; } ResourceReference<?> ref = rnode.getRes(); if (ref != null) { Resource<?,?> res = ref.get(); if (res != null && res.changed) { return true; } } } return false; } public static boolean checkForChanges() { for (JInternalFrame f : mdi.getAllFrames()) { if (f instanceof ResourceFrame) { if (((ResourceFrame<?,?>) f).resourceChanged() && f.isVisible()) { return true; } } } //TODO: See comment above. //return checkForChangesInTree(LGM.root); Iterator<?> it = currentFile.resMap.entrySet().iterator(); while (it.hasNext()) { Entry<?,?> pairs = (Map.Entry<?,?>)it.next(); if (pairs.getValue() instanceof ResourceList) { ResourceList<?> list = (ResourceList<?>) pairs.getValue(); for (Resource<?,?> res : list) { if (res.changed) return true; } } else if (pairs.getValue() instanceof SingletonResourceHolder) { SingletonResourceHolder<?> rh = (SingletonResourceHolder<?>) pairs.getValue(); Resource<?,?> res = rh.getResource(); if (res.changed) { return true; } } } return false; } /* * When the user saves, reset all the resources to their unsaved state. We do not check the frames * because they commit their changes allowing them to be written, while still allowing the user to * revert the frame if they so choose. * If the user has an open frame with changes basically, the save button will save the changes to * file and if the user saves the frame then they will still be asked to save when they close, if * they revert the changes to the frame they will exit right out. This is the expected behavior of * these functions. */ public static void resetChanges() { Iterator<?> it = currentFile.resMap.entrySet().iterator(); while (it.hasNext()) { Entry<?,?> pairs = (Map.Entry<?,?>)it.next(); if (pairs.getValue() instanceof ResourceList) { ResourceList<?> list = (ResourceList<?>) pairs.getValue(); for (Resource<?,?> res : list) { res.changed = false; } } else if (pairs.getValue() instanceof SingletonResourceHolder) { SingletonResourceHolder<?> rh = (SingletonResourceHolder<?>) pairs.getValue(); Resource<?,?> res = rh.getResource(); res.changed = false; } } } public static void askToSaveProject() { FileChooser fc = new FileChooser(); fc.save(LGM.currentFile.uri,LGM.currentFile.format); } public static void onMainFrameClosed() { if (!checkForChanges()) { System.exit(0); } int n = JOptionPane.showConfirmDialog(frame,Messages.getString("LGM.KEEPCHANGES_MESSAGE"), Messages.getString("LGM.KEEPCHANGES_TITLE"),JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,null); switch (n) { case JOptionPane.YES_OPTION: askToSaveProject(); System.exit(0); break; case JOptionPane.NO_OPTION: System.exit(0); break; case JOptionPane.CANCEL_OPTION: // do nothing break; } } static final class SplashProgress { final SplashScreen splash; final Graphics2D splashGraphics; final JProgressBar bar; final Graphics barGraphics; private String text = null; final boolean TIMER = System.getProperty("lgm.progresstimer") != null; //$NON-NLS-1$ private long startTime, completeTime; private ArrayList<Integer> progressValues; private ArrayList<Long> progressTimes; SplashProgress() { splash = SplashScreen.getSplashScreen(); if (splash != null) { splashGraphics = splash.createGraphics(); Dimension sss = splash.getSize(); Rectangle bb = new Rectangle(0,sss.height - 24,sss.width,24); bar = new JProgressBar(); bar.setBounds(bb); barGraphics = splashGraphics.create(bb.x,bb.y,bb.width,bb.height); } else { splashGraphics = null; bar = null; barGraphics = null; } if (TIMER) { progressValues = new ArrayList<Integer>(); progressTimes = new ArrayList<Long>(); } } void start() { if (TIMER) startTime = System.currentTimeMillis(); progress(0,Messages.getString("LGM.SPLASH_START")); //$NON-NLS-1$ } void complete() { if (TIMER) { completeTime = System.currentTimeMillis(); long tt = completeTime - startTime; System.out.print("Progress/% "); //$NON-NLS-1$ for (Integer v : progressValues) { System.out.print("\t" + v); //$NON-NLS-1$ } System.out.println(); System.out.print("Time/ms "); //$NON-NLS-1$ for (Long t : progressTimes) { System.out.print("\t" + t); //$NON-NLS-1$ } System.out.println(); System.out.print("Actual progress/%"); //$NON-NLS-1$ for (Long t : progressTimes) { System.out.print("\t" + Math.round(100.0 * t / tt)); //$NON-NLS-1$ } System.out.println(); } } void progress(int p) { progress(p,text); } void progress(int p, String t) { if (TIMER) { progressValues.add(p); progressTimes.add(System.currentTimeMillis() - startTime); } text = t; if (splash != null) { bar.setValue(p); bar.setStringPainted(t != null); bar.setString(t); update(); } } private void update() { bar.paint(barGraphics); splash.update(); } } private static boolean eventVisible = false; public static void showEventPanel() { if (Prefs.dockEventPanel) { eventVisible = !eventVisible; eventSelect.setVisible(eventVisible); setSelectedTab(treeTabs, Messages.getString("TreeFilter.TAB_EVENTS")); } else { if (eventFrame == null) { eventFrame = new JFrame(); eventFrame.setAlwaysOnTop(true); eventFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE); eventFrame.setSize(new Dimension(250,300)); eventFrame.setIconImage(LGM.getIconForKey("Toolbar.EVENT_BUTTON").getImage()); eventFrame.setTitle(Messages.getString("Toolbar.EVENT_BUTTON")); eventFrame.add(eventSelect); eventSelect.setVisible(true); eventSelect.setFloatable(false); eventFrame.setLocationRelativeTo(frame); } eventFrame.setVisible(true); } } public static void hideEventPanel() { if (eventFrame != null) { eventFrame.setVisible(false); } else { eventSelect.setVisible(false); } } public static void showPreferences() { if (prefFrame == null) { prefFrame = new PreferencesFrame(); } prefFrame.setVisible(true); } public static class MDIBackground extends JComponent { private static final long serialVersionUID = 1L; ImageIcon image; public MDIBackground(ImageIcon icon) { image = icon; if (image == null) return; if (image.getIconWidth() <= 0) image = null; } public int getWidth() { return LGM.mdi.getWidth(); } public int getHeight() { return LGM.mdi.getHeight(); } public void paintComponent(Graphics g) { super.paintComponent(g); if (image == null) return; for (int y = 0; y < getHeight(); y += image.getIconHeight()) for (int x = 0; x < getWidth(); x += image.getIconWidth()) g.drawImage(image.getImage(),x,y,null); } } public static void applyBackground(String bgloc) { ImageIcon bg = new ImageIcon(bgloc); if (bg.getIconWidth() == -1) { URL url = LGM.class.getClassLoader().getResource(bgloc); if (url != null) { bg = new ImageIcon(url); } } mdi.add(new MDIBackground(bg),JLayeredPane.FRAME_CONTENT_LAYER); } // Sets the default uncaught exception handler in case any threads forget to add one. public static void setDefaultExceptionHandler() { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { LGM.showDefaultExceptionHandler(e); } }); } // Adds a default uncaught exception handler to the current thread. This allows LGM to catch most // exceptions and properly display a stack trace for the user to file a bug report. public static void addDefaultExceptionHandler() { Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { public void uncaughtException(Thread t, Throwable e) { LGM.showDefaultExceptionHandler(e); } }); } // Show the default uncaught exception handler dialog to the user with a stack trace they can use // to submit a bug report. public static void showDefaultExceptionHandler(Throwable e) { System.err.println(Thread.currentThread().getName() + ": "); e.printStackTrace(); ErrorDialog errorDialog = ErrorDialog.getInstance(); if (!errorDialog.isVisible()) { errorDialog.setVisible(true); errorDialog.setDebugInfo(ErrorDialog.generateAgnosticInformation()); } errorDialog.appendDebugInfo(e); } public static GameSettings getSelectedConfig() { return (GameSettings) configsCombo.getSelectedItem(); } }