// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Frame; import java.awt.Image; import java.awt.Insets; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.dnd.DnDConstants; import java.awt.dnd.DropTarget; import java.awt.dnd.DropTargetDragEvent; import java.awt.dnd.DropTargetDropEvent; import java.awt.dnd.DropTargetEvent; import java.awt.dnd.DropTargetListener; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.OutputStream; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.concurrent.ExecutionException; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.JToolBar; import javax.swing.ProgressMonitor; import javax.swing.SwingUtilities; import javax.swing.SwingWorker; import javax.swing.UIManager; import javax.swing.UIManager.LookAndFeelInfo; import javax.swing.filechooser.FileFilter; import org.infinity.datatype.ProRef; import org.infinity.gui.BrowserMenuBar; import org.infinity.gui.ButtonPopupWindow; import org.infinity.gui.ChildFrame; import org.infinity.gui.InfinityTextArea; import org.infinity.gui.OpenFileFrame; import org.infinity.gui.PopupWindowEvent; import org.infinity.gui.PopupWindowListener; import org.infinity.gui.QuickSearch; import org.infinity.gui.ResourceTree; import org.infinity.gui.StatusBar; import org.infinity.gui.ViewFrame; import org.infinity.gui.WindowBlocker; import org.infinity.icon.Icons; import org.infinity.resource.Closeable; import org.infinity.resource.EffectFactory; import org.infinity.resource.Profile; import org.infinity.resource.Resource; import org.infinity.resource.ResourceFactory; import org.infinity.resource.Viewable; import org.infinity.resource.ViewableContainer; import org.infinity.resource.bcs.Compiler; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.resource.key.ResourceTreeModel; import org.infinity.resource.text.PlainTextResource; import org.infinity.search.SearchFrame; import org.infinity.updater.UpdateCheck; import org.infinity.updater.UpdateInfo; import org.infinity.updater.Updater; import org.infinity.updater.Utils; import org.infinity.util.FileDeletionHook; import org.infinity.util.IdsMapCache; import org.infinity.util.IniMapCache; import org.infinity.util.StringResource; import org.infinity.util.Table2daCache; import org.infinity.util.io.DlcManager; import org.infinity.util.io.FileManager; public final class NearInfinity extends JFrame implements ActionListener, ViewableContainer { private static final int[] JAVA_VERSION = {1, 8}; // the minimum java version supported private static final boolean DEBUG = false; // indicates whether to enable debugging features private static final InfinityTextArea consoletext = new InfinityTextArea(true); private static final String KEYFILENAME = "chitin.key"; private static final String WINDOW_SIZEX = "WindowSizeX"; private static final String WINDOW_SIZEY = "WindowSizeY"; private static final String WINDOW_POSX = "WindowPosX"; private static final String WINDOW_POSY = "WindowPosY"; private static final String WINDOW_STATE = "WindowState"; private static final String WINDOW_SPLITTER = "WindowSplitter"; private static final String LAST_GAMEDIR = "LastGameDir"; private static final String TABLE_WIDTH_ATTR = "TableColWidthAttr"; private static final String TABLE_WIDTH_OFS = "TableColWidthOfs"; private static final String TABLE_PANEL_HEIGHT = "TablePanelHeight"; private static final String STATUSBAR_TEXT_FMT = "Welcome to Near Infinity! - %1$s @ %2$s - %3$d files available"; private static NearInfinity browser; private final JPanel containerpanel; private final JSplitPane spSplitter; private final ResourceTree tree; private final StatusBar statusBar; private final WindowBlocker blocker = new WindowBlocker(this); // stores table column widths for "Attribute", "Value" and "Offset" private final int[] tableColumnWidth = { -1, -1, -1 }; private Viewable viewable; private ButtonPopupWindow bpwQuickSearch; private int tablePanelHeight; private ProgressMonitor pmProgress; private int progressIndex; public static boolean isDebug() { return DEBUG; } private static Path findKeyfile() { JFileChooser chooser; if (Profile.getGameRoot() == null) { chooser = new JFileChooser(new File(".")); } else { chooser = new JFileChooser(Profile.getGameRoot().toFile()); } chooser.setDialogTitle("Open game: Locate keyfile"); chooser.setFileFilter(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.isDirectory() || pathname.getName().toLowerCase(Locale.ENGLISH).endsWith(".key"); } @Override public String getDescription() { return "Infinity Keyfile (.KEY)"; } }); if (chooser.showOpenDialog(getInstance()) == JFileChooser.APPROVE_OPTION) { return chooser.getSelectedFile().toPath(); } return null; } public static InfinityTextArea getConsoleText() { return consoletext; } public static NearInfinity getInstance() { return browser; } /** Returns the NearInfinity version. */ public static String getVersion() { return BrowserMenuBar.VERSION; } public static void printHelp(String jarFile) { if (jarFile == null || jarFile.isEmpty() || !jarFile.toLowerCase(Locale.ENGLISH).endsWith(".jar")) { jarFile = "NearInfinity.jar"; } System.out.format("Usage: java -jar %1$s [game_path | options]", jarFile).println(); System.out.println("\nOptions:"); System.out.println(" -v, -version Display version information."); System.out.println(" -h, -help Display this help."); System.out.println("\nExamples:"); System.out.format("Specify game path: java -jar %1$s \"C:\\Games\\Baldurs Gate II\"", jarFile).println(); System.out.format("Display version: java -jar %1$s -v", jarFile).println(); System.out.format("Display help: java -jar %1$s -help", jarFile).println(); } /** Advances the progress monitor by one step with optional note. */ public static void advanceProgress(String note) { if (getInstance() != null && getInstance().pmProgress != null) { getInstance().pmProgress.setNote((note != null) ? note: ""); getInstance().pmProgress.setProgress(++getInstance().progressIndex); } } public static void main(String args[]) { // evaluating command line arguments for (final String arg: args) { if (arg.equalsIgnoreCase("-v") || arg.equalsIgnoreCase("-version")) { System.out.println("Near Infinity " + getVersion()); System.exit(0); } if (arg.equalsIgnoreCase("-h") || arg.equalsIgnoreCase("-help")) { String jarFile = Utils.getJarFileName(NearInfinity.class); if (!jarFile.isEmpty()) { jarFile = FileManager.resolve(jarFile).getFileName().toString(); } printHelp(jarFile); System.exit(0); } } String[] javaVersion = System.getProperty("java.specification.version").split("\\."); try { for (int i = 0; i < Math.min(JAVA_VERSION.length, javaVersion.length); i++) { if (Integer.parseInt(javaVersion[i]) < JAVA_VERSION[i]) { JOptionPane.showMessageDialog(null, String.format("Version %1$d.%2$d or newer of Java is required!", JAVA_VERSION[0], JAVA_VERSION[1]), "Error", JOptionPane.ERROR_MESSAGE); System.exit(10); } } } catch (Exception e) { // Try starting anyway if the test goes sour e.printStackTrace(); } System.setOut(new ConsoleStream(System.out, consoletext)); System.setErr(new ConsoleStream(System.err, consoletext)); // Override game folder via application parameter Path gameOverride = null; if (args.length > 0) { Path f = FileManager.resolve(args[0]); if (Files.isRegularFile(f)) { f = f.getParent(); } if (Files.isDirectory(f)) { gameOverride = f; } } new NearInfinity(gameOverride); } private NearInfinity(Path gameOverride) { super("Near Infinity"); browser = this; setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); setAppIcon(); // FileDeletionHook provides a way to delete files when the Java Virtual Machine shuts down Runtime.getRuntime().addShutdownHook(FileDeletionHook.getInstance()); // Migrate preferences from "infinity" to "org.infinity" if needed Preferences prefs = Preferences.userNodeForPackage(getClass()); migratePreferences("infinity", prefs, true); new BrowserMenuBar(); setJMenuBar(BrowserMenuBar.getInstance()); String lastDir = null; if (gameOverride != null && Files.isDirectory(gameOverride)) { lastDir = gameOverride.toString(); } else { lastDir = prefs.get(LAST_GAMEDIR, null); } final Path keyFile; Path path; if (Files.isRegularFile(path = FileManager.resolve(KEYFILENAME))) { keyFile = path; } else if (lastDir != null && Files.isRegularFile(path = FileManager.resolve(lastDir, KEYFILENAME))) { keyFile = path; } else { keyFile = findKeyfile(); } if (keyFile == null) { System.exit(10); } showProgress("Starting Near Infinity", 6); SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() { @Override protected Void doInBackground() throws Exception { Profile.openGame(keyFile, BrowserMenuBar.getInstance().getBookmarkName(keyFile)); advanceProgress("Initializing GUI..."); BrowserMenuBar.getInstance().gameLoaded(Profile.Game.Unknown, null); Compiler.restartCompiler(); return null; } }; try { try { worker.execute(); worker.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); System.exit(10); } addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent event) { if (removeViewable()) { storePreferences(); ChildFrame.closeWindows(); System.exit(0); } } }); try { LookAndFeelInfo info = BrowserMenuBar.getInstance().getLookAndFeel(); UIManager.setLookAndFeel(info.getClassName()); SwingUtilities.updateComponentTreeUI(this); } catch (Exception e) { e.printStackTrace(); } statusBar = new StatusBar(); ResourceTreeModel treemodel = ResourceFactory.getResources(); updateWindowTitle(); final String msg = String.format(STATUSBAR_TEXT_FMT, Profile.getProperty(Profile.Key.GET_GAME_TITLE), Profile.getGameRoot(), treemodel.size()); statusBar.setMessage(msg); tree = new ResourceTree(treemodel); tree.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3)); JToolBar toolBar = new JToolBar("Navigation", JToolBar.HORIZONTAL); JButton b; toolBar.setRollover(true); toolBar.setFloatable(false); b = new JButton(Icons.getIcon(Icons.ICON_EXPAND_16)); b.addActionListener(this); b.setActionCommand("Expand"); b.setToolTipText("Expand selected node"); b.setMargin(new Insets(4, 4, 4, 4)); toolBar.add(b); b = new JButton(Icons.getIcon(Icons.ICON_COLLAPSE_16)); b.addActionListener(this); b.setActionCommand("Collapse"); b.setToolTipText("Collapse selected node"); b.setMargin(new Insets(4, 4, 4, 4)); toolBar.add(b); toolBar.addSeparator(new Dimension(8, 24)); b = new JButton(Icons.getIcon(Icons.ICON_EXPAND_ALL_24)); b.addActionListener(this); b.setActionCommand("ExpandAll"); b.setToolTipText("Expand all"); b.setMargin(new Insets(0, 0, 0, 0)); toolBar.add(b); b = new JButton(Icons.getIcon(Icons.ICON_COLLAPSE_ALL_24)); b.addActionListener(this); b.setActionCommand("CollapseAll"); b.setToolTipText("Collapse all"); b.setMargin(new Insets(0, 0, 0, 0)); toolBar.add(b); toolBar.addSeparator(new Dimension(8, 24)); bpwQuickSearch = new ButtonPopupWindow(Icons.getIcon(Icons.ICON_MAGNIFY_16)); bpwQuickSearch.setToolTipText("Find resource"); bpwQuickSearch.setMargin(new Insets(4, 4, 4, 4)); toolBar.add(bpwQuickSearch); bpwQuickSearch.addPopupWindowListener(new PopupWindowListener() { @Override public void popupWindowWillBecomeVisible(PopupWindowEvent event) { // XXX: Working around a visual glitch in QuickSearch's JComboBox popup list // by creating new QuickSearch instances on activation bpwQuickSearch.setContent(new QuickSearch(bpwQuickSearch, tree)); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { Component c = bpwQuickSearch.getContent(); if (c != null) { c.requestFocusInWindow(); } } }); } @Override public void popupWindowWillBecomeInvisible(PopupWindowEvent event) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { bpwQuickSearch.setContent(null); tree.requestFocusInWindow(); } }); } }); JPanel leftPanel = new JPanel(new BorderLayout()); leftPanel.add(tree, BorderLayout.CENTER); leftPanel.add(toolBar, BorderLayout.NORTH); containerpanel = new JPanel(new BorderLayout()); spSplitter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, containerpanel); spSplitter.setBorder(BorderFactory.createEmptyBorder()); spSplitter.setDividerLocation(prefs.getInt(WINDOW_SPLITTER, 200)); Container pane = getContentPane(); pane.setLayout(new BorderLayout()); pane.add(spSplitter, BorderLayout.CENTER); pane.add(statusBar, BorderLayout.SOUTH); } finally { hideProgress(); } setSize(prefs.getInt(WINDOW_SIZEX, 930), prefs.getInt(WINDOW_SIZEY, 700)); int centerX = (int)Toolkit.getDefaultToolkit().getScreenSize().getWidth() - getSize().width >> 1; int centerY = (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight() - getSize().height >> 1; setLocation(prefs.getInt(WINDOW_POSX, centerX), prefs.getInt(WINDOW_POSY, centerY)); setVisible(true); setExtendedState(prefs.getInt(WINDOW_STATE, NORMAL)); tableColumnWidth[0] = Math.max(15, prefs.getInt(TABLE_WIDTH_ATTR, 300)); tableColumnWidth[1] = 0; tableColumnWidth[2] = Math.max(15, prefs.getInt(TABLE_WIDTH_OFS, 100)); tablePanelHeight = Math.max(50, prefs.getInt(TABLE_PANEL_HEIGHT, 250)); // enabling file drag and drop for whole window new DropTarget(getRootPane(), new FileDropTargetListener()); // Checking for updates if (Updater.getInstance().isAutoUpdateCheckEnabled() && Updater.getInstance().hasAutoUpdateCheckDateExpired()) { // storing last check date for future reference Updater.getInstance().setAutoUpdateCheckDate(null); // running check in background with as little as possible interference with user interactions new Thread(new Runnable() { @Override public void run() { UpdateInfo info = Updater.getInstance().loadUpdateInfo(); if (info != null) { if (Updater.isNewRelease(info.getRelease(), true)) { UpdateCheck.showDialog(NearInfinity.getInstance(), info); } } } }).start(); } SwingUtilities.invokeLater(new Runnable() { @Override public void run() { tree.requestFocusInWindow(); } }); } // --------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getActionCommand().equals("Open")) { blocker.setBlocked(true); Profile.Game oldGame = Profile.getGame(); String oldFile = Profile.getChitinKey().toString(); if (reloadFactory(false)) { if (!removeViewable()) { blocker.setBlocked(false); return; } ChildFrame.closeWindows(); ResourceTreeModel treemodel = ResourceFactory.getResources(); updateWindowTitle(); final String msg = String.format(STATUSBAR_TEXT_FMT, Profile.getProperty(Profile.Key.GET_GAME_TITLE), Profile.getGameRoot(), treemodel.size()); statusBar.setMessage(msg); BrowserMenuBar.getInstance().gameLoaded(oldGame, oldFile); tree.setModel(treemodel); containerpanel.removeAll(); containerpanel.revalidate(); containerpanel.repaint(); } blocker.setBlocked(false); } else if (event.getActionCommand().equals("Exit")) { quit(); } else if (event.getActionCommand().equals("GameIni")) { editGameIni(this); } else if (event.getActionCommand().equals("Refresh")) { refreshGame(); } else if (event.getActionCommand().equals("ChangeLook")) { try { LookAndFeelInfo info = BrowserMenuBar.getInstance().getLookAndFeel(); UIManager.setLookAndFeel(info.getClassName()); SwingUtilities.updateComponentTreeUI(this); ChildFrame.updateWindowGUIs(); tree.reloadRenderer(); tree.repaint(); JOptionPane.showMessageDialog(this, "It might be necessary to restart Near Infinity\n" + "to completely change look and feel."); } catch (Exception e) { e.printStackTrace(); } } else if (event.getActionCommand().equals("Collapse")) { try { WindowBlocker.blockWindow(this, true); tree.collapseSelected(); tree.requestFocusInWindow(); } finally { WindowBlocker.blockWindow(this, false); } } else if (event.getActionCommand().equals("Expand")) { try { WindowBlocker.blockWindow(this, true); tree.expandSelected(); tree.requestFocusInWindow(); } finally { WindowBlocker.blockWindow(this, false); } } else if (event.getActionCommand().equals("CollapseAll")) { try { WindowBlocker.blockWindow(this, true); tree.collapseAll(); tree.requestFocusInWindow(); } finally { WindowBlocker.blockWindow(this, false); } } else if (event.getActionCommand().equals("ExpandAll")) { try { WindowBlocker.blockWindow(this, true); tree.expandAll(); tree.requestFocusInWindow(); } finally { WindowBlocker.blockWindow(this, false); } } } // --------------------- End Interface ActionListener --------------------- // --------------------- Begin Interface ViewableContainer --------------------- @Override public StatusBar getStatusBar() { return statusBar; } @Override public Viewable getViewable() { return viewable; } @Override public void setViewable(Viewable newViewable) { if (newViewable == null || !(newViewable instanceof Resource)) removeViewable(); else { Resource resource = (Resource)newViewable; if (viewable != null) { if (resource.getResourceEntry() == ((Resource)viewable).getResourceEntry()) return; else if (viewable instanceof Closeable) { try { ((Closeable)viewable).close(); } catch (Exception e) { if (viewable instanceof Resource) tree.select(((Resource)viewable).getResourceEntry()); return; } } } viewable = newViewable; tree.select(resource.getResourceEntry()); BrowserMenuBar.getInstance().resourceShown(resource); statusBar.setMessage(resource.getResourceEntry().getActualPath().toString()); containerpanel.removeAll(); containerpanel.add(viewable.makeViewer(this), BorderLayout.CENTER); containerpanel.revalidate(); containerpanel.repaint(); toFront(); } } // --------------------- End Interface ViewableContainer --------------------- public ResourceTree getResourceTree() { return tree; } public void openGame(Path keyFile) { blocker.setBlocked(true); try { Profile.Game oldGame = Profile.getGame(); String oldFile = Profile.getChitinKey().toString(); clearCache(); EffectFactory.init(); Profile.openGame(keyFile, BrowserMenuBar.getInstance().getBookmarkName(keyFile)); Compiler.restartCompiler(); removeViewable(); ChildFrame.closeWindows(); ResourceTreeModel treemodel = ResourceFactory.getResources(); updateWindowTitle(); final String msg = String.format(STATUSBAR_TEXT_FMT, Profile.getProperty(Profile.Key.GET_GAME_TITLE), Profile.getGameRoot(), treemodel.size()); statusBar.setMessage(msg); BrowserMenuBar.getInstance().gameLoaded(oldGame, oldFile); tree.setModel(treemodel); containerpanel.removeAll(); containerpanel.revalidate(); containerpanel.repaint(); } finally { blocker.setBlocked(false); } } public boolean removeViewable() { if (viewable != null && viewable instanceof Closeable) { try { ((Closeable)viewable).close(); } catch (Exception e) { return false; } } viewable = null; BrowserMenuBar.getInstance().resourceShown(null); tree.select(null); containerpanel.removeAll(); containerpanel.revalidate(); containerpanel.repaint(); return true; } public void showResourceEntry(ResourceEntry resourceEntry) { tree.select(resourceEntry); } public void quit() { if (removeViewable()) { ChildFrame.closeWindows(); storePreferences(); clearCache(); System.exit(0); } } // Re-initializes currently selected game public void refreshGame() { try { blocker.setBlocked(true); reloadFactory(true); if (removeViewable()) { ChildFrame.closeWindows(); ResourceTreeModel treemodel = ResourceFactory.getResources(); final String msg = String.format(STATUSBAR_TEXT_FMT, Profile.getProperty(Profile.Key.GET_GAME_TITLE), Profile.getGameRoot(), treemodel.size()); statusBar.setMessage(msg); BrowserMenuBar.getInstance().gameLoaded(null, null); tree.setModel(treemodel); containerpanel.removeAll(); containerpanel.revalidate(); containerpanel.repaint(); } } finally { blocker.setBlocked(false); } } // Set/Reset main window title public void updateWindowTitle() { String title = Profile.getProperty(Profile.Key.GET_GAME_TITLE); String desc = Profile.getProperty(Profile.Key.GET_GAME_DESC); if (desc != null && !desc.isEmpty()) { setTitle(String.format("Near Infinity - %1$s (%2$s)", title, desc)); } else { setTitle(String.format("Near Infinity - %1$s", title)); } } // Opens game's ini file in text editor public boolean editGameIni(Component parent) { boolean retVal = false; Path iniFile = Profile.getProperty(Profile.Key.GET_GAME_INI_FILE); try { if (iniFile != null && Files.isRegularFile(iniFile)) { new ViewFrame(parent, new PlainTextResource(new FileResourceEntry(iniFile))); } else { throw new Exception(); } } catch (Exception e) { JOptionPane.showMessageDialog(parent, "Cannot open INI file.", "Error", JOptionPane.ERROR_MESSAGE); } return retVal; } /** * Returns the current column width for tables of structured resources. * @param index The column index (0 = Attribute, 1 = Value and, optionally, 2 = Offset) */ public int getTableColumnWidth(int index) { index = Math.min(Math.max(0, index), 2); if (tableColumnWidth[index] < 0) { switch (index) { case 0: // "Attribute" column tableColumnWidth[index] = 300; break; case 1: // "Value" column (calculated dynamically) tableColumnWidth[index] = 0; break; case 2: // optional "Offset" column tableColumnWidth[index] = 100; break; } } return tableColumnWidth[index]; } /** * Updates the current default column width for tables of structured resources. * @param index The column index (0 = Attribute, 1 = Value and, optionally, 2 = Offset) * @param newValue New width in pixels for the specified column * @return Old column width */ public int updateTableColumnWidth(int index, int newValue) { index = Math.min(Math.max(0, index), 2); int retVal = tableColumnWidth[index]; tableColumnWidth[index] = Math.max(15, newValue); return retVal; } /** Returns the current height of the panel below the table of structured resources.*/ public int getTablePanelHeight() { return tablePanelHeight; } /** * Updates the height of the panel below the table of structured resources. */ public int updateTablePanelHeight(int newValue) { newValue = Math.max(50, newValue); int retVal = tablePanelHeight; tablePanelHeight = newValue; return retVal; } private static boolean reloadFactory(boolean refreshonly) { boolean retVal = false; clearCache(); Path keyFile = refreshonly ? Profile.getChitinKey() : findKeyfile(); if (keyFile != null) { EffectFactory.init(); retVal = Profile.openGame(keyFile, BrowserMenuBar.getInstance().getBookmarkName(keyFile)); if (retVal) { Compiler.restartCompiler(); } } return retVal; } // Central method for clearing cached data private static void clearCache() { if (ResourceFactory.getKeyfile() != null) { ResourceFactory.getKeyfile().closeBIFFFiles(); } DlcManager.close(); FileManager.reset(); IdsMapCache.clearCache(); IniMapCache.clearCache(); Table2daCache.clearCache(); SearchFrame.clearCache(); StringResource.close(); ProRef.clearCache(); } private static void showProgress(String msg, int max) { if (getInstance() != null && getInstance().pmProgress == null) { getInstance().pmProgress = new ProgressMonitor(null, msg, " ", 0, max); getInstance().pmProgress.setMillisToDecideToPopup(0); getInstance().pmProgress.setMillisToPopup(0); getInstance().progressIndex = 0; getInstance().pmProgress.setProgress(++getInstance().progressIndex); } } private static void hideProgress() { if (getInstance() != null && getInstance().pmProgress != null) { getInstance().pmProgress.close(); getInstance().pmProgress = null; } } private void storePreferences() { Preferences prefs = Preferences.userNodeForPackage(getClass()); // preserve non-maximized size and position of the window if possible if ((getExtendedState() & Frame.MAXIMIZED_HORIZ) == 0) { prefs.putInt(WINDOW_SIZEX, (int)getSize().getWidth()); prefs.putInt(WINDOW_POSX, (int)getLocation().getX()); } if ((getExtendedState() & Frame.MAXIMIZED_VERT) == 0) { prefs.putInt(WINDOW_SIZEY, (int)getSize().getHeight()); prefs.putInt(WINDOW_POSY, (int)getLocation().getY()); } prefs.putInt(WINDOW_STATE, getExtendedState()); prefs.putInt(WINDOW_SPLITTER, spSplitter.getDividerLocation()); prefs.put(LAST_GAMEDIR, Profile.getGameRoot().toString()); prefs.putInt(TABLE_WIDTH_ATTR, getTableColumnWidth(0)); prefs.putInt(TABLE_WIDTH_OFS, getTableColumnWidth(2)); prefs.putInt(TABLE_PANEL_HEIGHT, getTablePanelHeight()); BrowserMenuBar.getInstance().storePreferences(); Updater.getInstance().saveUpdateSettings(); } private void setAppIcon() { List<Image> list = new ArrayList<Image>(); for (int i = 4; i < 8; i++) { list.add(Icons.getImage(String.format("App%1$d.png", 1 << i))); } setIconImages(list); } // Migrate preferences from sourceNode to the currently used prefs node if needed. // Returns true if content of sourceNode has been cloned into the current node. private boolean migratePreferences(String sourceNode, Preferences curPrefs, boolean showError) { boolean retVal = false; if (sourceNode != null && !sourceNode.isEmpty() && curPrefs != null) { Preferences prefsOld = null; boolean isPrefsEmpty = false; try { isPrefsEmpty = (curPrefs.keys().length == 0); sourceNode = sourceNode.trim(); if (Preferences.userRoot().nodeExists(sourceNode)) { prefsOld = Preferences.userRoot().node(sourceNode); } } catch (Exception e) { prefsOld = null; e.printStackTrace(); } if (isPrefsEmpty && prefsOld != null && !prefsOld.equals(curPrefs)) { try { clonePrefsNode(prefsOld, curPrefs); retVal = true; } catch (Exception e) { retVal = false; try { curPrefs.clear(); } catch (BackingStoreException bse) {} e.printStackTrace(); if (showError) { JOptionPane.showMessageDialog(this, "Error migrating old Near Infinity settings. Using defaults.", "Error", JOptionPane.ERROR_MESSAGE); } } } } return retVal; } // Duplicates content from prefsOld to prefsNew recursively private void clonePrefsNode(Preferences prefsOld, Preferences prefsNew) throws Exception { if (prefsOld != null && prefsNew != null && !prefsOld.equals(prefsNew)) { // cloning keys String[] keyNames = prefsOld.keys(); if (keyNames != null) { for (int i = 0; i < keyNames.length; i++) { String value = prefsOld.get(keyNames[i], null); if (value != null) { prefsNew.put(keyNames[i], value); } } } // cloning child nodes String[] childNames = prefsOld.childrenNames(); if (childNames != null) { for (int i = 0; i < childNames.length; i++) { clonePrefsNode(prefsOld.node(childNames[i]), prefsNew.node(childNames[i])); } } } } // -------------------------- INNER CLASSES -------------------------- private static final class ConsoleStream extends PrintStream { private final JTextArea text; private ConsoleStream(OutputStream out, JTextArea text) { super(out); this.text = text; } @Override public void write(byte buf[], int off, int len) { super.write(buf, off, len); try { text.append(new String(buf, off, len)); } catch (Exception e) { e.printStackTrace(); } } } private class FileDropTargetListener implements DropTargetListener, Runnable { private List<File> files; private FileDropTargetListener() { } @Override public void dragEnter(DropTargetDragEvent event) { } @Override public void dragOver(DropTargetDragEvent event) { } @Override public void dropActionChanged(DropTargetDragEvent event) { } @Override public void dragExit(DropTargetEvent event) { } @SuppressWarnings("unchecked") @Override public void drop(DropTargetDropEvent event) { if (event.isLocalTransfer() || !event.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { event.rejectDrop(); return; } try { event.acceptDrop(DnDConstants.ACTION_COPY); files = (List<File>)event.getTransferable().getTransferData(DataFlavor.javaFileListFlavor); } catch (Exception e) { e.printStackTrace(); event.dropComplete(false); } event.dropComplete(true); if (files != null && files.size() == 1) { Path path = files.get(0).toPath(); if (path != null && Files.isRegularFile(path) && path.getFileName().toString().toUpperCase(Locale.ENGLISH).endsWith(".KEY")) { Path curFile = Profile.getChitinKey(); if (!path.equals(curFile)) { int ret = JOptionPane.showConfirmDialog(NearInfinity.getInstance(), "Open game \"" + path + "\"?", "Open game", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (ret == JOptionPane.YES_OPTION) { NearInfinity.getInstance().openGame(path); } } return; } } new Thread(this).start(); } @Override public void run() { if (files != null) { files.forEach((file) -> { Path path = file.toPath(); if (Files.isRegularFile(path)) { OpenFileFrame.openExternalFile(NearInfinity.getInstance(), path); } }); } } } }