removed UI
This commit is contained in:
parent
ccd50b0275
commit
0f138f61a8
34 changed files with 57 additions and 6219 deletions
|
@ -1,14 +1,8 @@
|
||||||
package net.vhati.modmanager;
|
package net.vhati.modmanager;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import javax.swing.JFileChooser;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.LookAndFeel;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.UIManager;
|
|
||||||
|
|
||||||
import ch.qos.logback.classic.LoggerContext;
|
import ch.qos.logback.classic.LoggerContext;
|
||||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||||
|
@ -20,9 +14,6 @@ import org.slf4j.bridge.SLF4JBridgeHandler;
|
||||||
|
|
||||||
import net.vhati.modmanager.cli.SlipstreamCLI;
|
import net.vhati.modmanager.cli.SlipstreamCLI;
|
||||||
import net.vhati.modmanager.core.ComparableVersion;
|
import net.vhati.modmanager.core.ComparableVersion;
|
||||||
import net.vhati.modmanager.core.FTLUtilities;
|
|
||||||
import net.vhati.modmanager.core.SlipstreamConfig;
|
|
||||||
import net.vhati.modmanager.ui.ManagerFrame;
|
|
||||||
|
|
||||||
|
|
||||||
public class FTLModManager {
|
public class FTLModManager {
|
||||||
|
@ -75,271 +66,10 @@ public class FTLModManager {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if ( args.length > 0 ) {
|
if (args.length == 0 ) {
|
||||||
|
System.out.println("Project called with no arguments.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
SlipstreamCLI.main( args );
|
SlipstreamCLI.main( args );
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure all popups are triggered from the event dispatch thread.
|
|
||||||
SwingUtilities.invokeLater(FTLModManager::guiInit);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static void guiInit() {
|
|
||||||
try {
|
|
||||||
// TODO: get mods file from env var
|
|
||||||
// Nag if the jar was double-clicked.
|
|
||||||
if (!new File("./mods/").exists()) {
|
|
||||||
String currentPath = new File( "." ).getAbsoluteFile().getParentFile().getAbsolutePath();
|
|
||||||
|
|
||||||
log.error( String.format( "Slipstream could not find its own folder (Currently in \"%s\"), exiting...", currentPath ) );
|
|
||||||
showErrorDialog( String.format( "Slipstream could not find its own folder.\nCurrently in: %s\n\nRun one of the following instead of the jar...\nWindows: modman.exe or modman_admin.exe\nLinux/OSX: modman.command or modman-cli.sh\n\nSlipstream will now exit.", currentPath ) );
|
|
||||||
|
|
||||||
throw new ExitException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: get config file from env var
|
|
||||||
File configFile = new File( "modman.cfg" );
|
|
||||||
|
|
||||||
SlipstreamConfig appConfig = new SlipstreamConfig(configFile);
|
|
||||||
|
|
||||||
// Look-and-Feel.
|
|
||||||
boolean useDefaultUI = Boolean.parseBoolean(appConfig.getProperty(SlipstreamConfig.USE_DEFAULT_UI, "false"));
|
|
||||||
|
|
||||||
if ( !useDefaultUI ) {
|
|
||||||
LookAndFeel defaultLaf = UIManager.getLookAndFeel();
|
|
||||||
log.debug( "Default look and feel is: "+ defaultLaf.getName() );
|
|
||||||
|
|
||||||
try {
|
|
||||||
log.debug( "Setting system look and feel: "+ UIManager.getSystemLookAndFeelClassName() );
|
|
||||||
|
|
||||||
// SystemLaf is risky. It may throw an exception, or lead to graphical bugs.
|
|
||||||
// Problems are generally caused by custom Windows themes.
|
|
||||||
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
log.error( "Failed to set system look and feel", e );
|
|
||||||
log.info( "Setting "+ SlipstreamConfig.USE_DEFAULT_UI +"=true in the config file to prevent this error..." );
|
|
||||||
|
|
||||||
appConfig.setProperty( SlipstreamConfig.USE_DEFAULT_UI, "true" );
|
|
||||||
|
|
||||||
try {
|
|
||||||
UIManager.setLookAndFeel( defaultLaf );
|
|
||||||
}
|
|
||||||
catch ( Exception f ) {
|
|
||||||
log.error( "Error returning to the default look and feel after failing to set system look and feel", f );
|
|
||||||
|
|
||||||
// Write an emergency config and exit.
|
|
||||||
try {
|
|
||||||
appConfig.writeConfig();
|
|
||||||
}
|
|
||||||
catch ( IOException g ) {
|
|
||||||
log.error( String.format( "Error writing config to \"%s\"", configFile.getPath(), g ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ExitException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug( "Using default Look and Feel" );
|
|
||||||
}
|
|
||||||
|
|
||||||
// FTL Resources Path.
|
|
||||||
File datsDir = null;
|
|
||||||
String datsPath = appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH, "" );
|
|
||||||
|
|
||||||
if ( datsPath.length() > 0 ) {
|
|
||||||
log.info( "Using FTL dats path from config: "+ datsPath );
|
|
||||||
datsDir = new File( datsPath );
|
|
||||||
if ( FTLUtilities.isDatsDirValid( datsDir ) == false ) {
|
|
||||||
log.error( "The config's "+ SlipstreamConfig.FTL_DATS_PATH +" does not exist, or it is invalid" );
|
|
||||||
datsDir = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
log.debug( "No "+ SlipstreamConfig.FTL_DATS_PATH +" previously set" );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find/prompt for the path to set in the config.
|
|
||||||
if ( datsDir == null ) {
|
|
||||||
datsDir = FTLUtilities.findDatsDir();
|
|
||||||
if ( datsDir != null ) {
|
|
||||||
int response = JOptionPane.showConfirmDialog( null, "FTL resources were found in:\n"+ datsDir.getPath() +"\nIs this correct?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
|
|
||||||
if ( response == JOptionPane.NO_OPTION ) datsDir = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( datsDir == null ) {
|
|
||||||
log.debug( "FTL dats path was not located automatically. Prompting user for location" );
|
|
||||||
datsDir = FTLUtilities.promptForDatsDir( null );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( datsDir != null ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.FTL_DATS_PATH, datsDir.getAbsolutePath() );
|
|
||||||
log.info( "FTL dats located at: "+ datsDir.getAbsolutePath() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( datsDir == null ) {
|
|
||||||
showErrorDialog( "FTL resources were not found.\nSlipstream will now exit." );
|
|
||||||
log.debug( "No FTL dats path found, exiting" );
|
|
||||||
|
|
||||||
throw new ExitException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ask about Steam.
|
|
||||||
if ( appConfig.getProperty( SlipstreamConfig.STEAM_DISTRO, "" ).length() == 0 ) {
|
|
||||||
int steamBasedResponse = JOptionPane.showConfirmDialog( null, "Was FTL installed via Steam?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
|
|
||||||
if ( steamBasedResponse == JOptionPane.YES_OPTION ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "true" );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.STEAM_DISTRO, "false" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a Steam distro.
|
|
||||||
if ( "true".equals( appConfig.getProperty( SlipstreamConfig.STEAM_DISTRO, "false" ) ) ) {
|
|
||||||
|
|
||||||
// Find Steam's executable.
|
|
||||||
if ( appConfig.getProperty( SlipstreamConfig.STEAM_EXE_PATH, "" ).length() == 0 ) {
|
|
||||||
|
|
||||||
File steamExeFile = FTLUtilities.findSteamExe();
|
|
||||||
|
|
||||||
if ( steamExeFile == null && System.getProperty( "os.name" ).startsWith( "Windows" ) ) {
|
|
||||||
try {
|
|
||||||
String registryExePath = FTLUtilities.queryRegistryKey( "HKCU\\Software\\Valve\\Steam", "SteamExe", "REG_SZ" );
|
|
||||||
if ( registryExePath != null && !(steamExeFile=new File( registryExePath )).exists() ) {
|
|
||||||
steamExeFile = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch( IOException e ) {
|
|
||||||
log.error( "Error while querying registry for Steam's path", e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( steamExeFile != null ) {
|
|
||||||
int response = JOptionPane.showConfirmDialog( null, "Steam was found at:\n"+ steamExeFile.getPath() +"\nIs this correct?", "Confirm", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
|
|
||||||
if ( response == JOptionPane.NO_OPTION ) steamExeFile = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( steamExeFile == null ) {
|
|
||||||
log.debug( "Steam was not located automatically. Prompting user for location" );
|
|
||||||
|
|
||||||
String steamPrompt = ""
|
|
||||||
+ "You will be prompted to locate Steam's executable.\n"
|
|
||||||
+ "- Windows: Steam.exe\n"
|
|
||||||
+ "- Linux: steam\n"
|
|
||||||
+ "- OSX: Steam.app\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "If you can't find it, you can cancel and set it later.";
|
|
||||||
JOptionPane.showMessageDialog( null, steamPrompt, "Find Steam", JOptionPane.INFORMATION_MESSAGE );
|
|
||||||
|
|
||||||
JFileChooser steamExeChooser = new JFileChooser();
|
|
||||||
steamExeChooser.setDialogTitle( "Find Steam.exe or steam or Steam.app" );
|
|
||||||
steamExeChooser.setFileHidingEnabled( false );
|
|
||||||
steamExeChooser.setMultiSelectionEnabled( false );
|
|
||||||
|
|
||||||
if ( steamExeChooser.showOpenDialog( null ) == JFileChooser.APPROVE_OPTION ) {
|
|
||||||
steamExeFile = steamExeChooser.getSelectedFile();
|
|
||||||
if ( !steamExeFile.exists() ) steamExeFile = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( steamExeFile != null ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.STEAM_EXE_PATH, steamExeFile.getAbsolutePath() );
|
|
||||||
log.info( "Steam located at: "+ steamExeFile.getAbsolutePath() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( appConfig.getProperty( SlipstreamConfig.STEAM_EXE_PATH, "" ).length() > 0 ) {
|
|
||||||
|
|
||||||
if ( appConfig.getProperty( SlipstreamConfig.RUN_STEAM_FTL, "" ).length() == 0 ) {
|
|
||||||
|
|
||||||
String[] launchOptions = new String[] {"Directly", "Steam"};
|
|
||||||
int launchResponse = JOptionPane.showOptionDialog( null, "Would you prefer to launch FTL directly, or via Steam?", "How to Launch?", JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, launchOptions, launchOptions[1] );
|
|
||||||
if ( launchResponse == 0 ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" );
|
|
||||||
}
|
|
||||||
else if ( launchResponse == 1 ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, "true" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt if update_catalog is invalid or hasn't been set.
|
|
||||||
boolean askAboutUpdates = false;
|
|
||||||
if ( !appConfig.getProperty( SlipstreamConfig.UPDATE_CATALOG, "" ).matches( "^\\d+$" ) )
|
|
||||||
askAboutUpdates = true;
|
|
||||||
if ( !appConfig.getProperty( SlipstreamConfig.UPDATE_APP, "" ).matches( "^\\d+$" ) )
|
|
||||||
askAboutUpdates = true;
|
|
||||||
|
|
||||||
if ( askAboutUpdates ) {
|
|
||||||
String updatePrompt = ""
|
|
||||||
+ "Would you like Slipstream to periodically check for updates?\n"
|
|
||||||
+ "\n"
|
|
||||||
+ "You can change this later.";
|
|
||||||
|
|
||||||
int response = JOptionPane.showConfirmDialog( null, updatePrompt, "Updates", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
|
|
||||||
if ( response == JOptionPane.YES_OPTION ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.UPDATE_CATALOG, "7" );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.UPDATE_APP, "4" );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.UPDATE_CATALOG, "0" );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.UPDATE_APP, "0" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ManagerFrame frame = null;
|
|
||||||
try {
|
|
||||||
frame = new ManagerFrame( appConfig, APP_NAME, APP_VERSION, APP_URL, APP_AUTHOR );
|
|
||||||
frame.init();
|
|
||||||
frame.setVisible( true );
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
log.error( "Failed to create and init the main window", e );
|
|
||||||
|
|
||||||
// If the frame is constructed, but an exception prevents it
|
|
||||||
// becoming visible, that *must* be caught. The frame registers
|
|
||||||
// itself as a global uncaught exception handler. It doesn't
|
|
||||||
// dispose() itself in the handler, so EDT will wait forever
|
|
||||||
// for an invisible window to close.
|
|
||||||
|
|
||||||
if ( frame != null && frame.isDisplayable() ) {
|
|
||||||
frame.setDisposeNormally( false );
|
|
||||||
frame.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ExitException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( ExitException e ) {
|
|
||||||
System.gc();
|
|
||||||
// System.exit( 1 ); // Don't do this (InterruptedException). Let EDT end gracefully.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void showErrorDialog( String message ) {
|
|
||||||
JOptionPane.showMessageDialog( null, message, "Error", JOptionPane.ERROR_MESSAGE );
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ExitException extends RuntimeException {
|
|
||||||
public ExitException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExitException( String message ) {
|
|
||||||
super( message );
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExitException( Throwable cause ) {
|
|
||||||
super( cause );
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExitException( String message, Throwable cause ) {
|
|
||||||
super( message, cause );
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,8 @@ import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.zip.ZipEntry;
|
import java.util.zip.ZipEntry;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
@ -41,24 +37,20 @@ import net.vhati.modmanager.core.Report.ReportFormatter;
|
||||||
import net.vhati.modmanager.core.Report.ReportMessage;
|
import net.vhati.modmanager.core.Report.ReportMessage;
|
||||||
import net.vhati.modmanager.core.SlipstreamConfig;
|
import net.vhati.modmanager.core.SlipstreamConfig;
|
||||||
|
|
||||||
|
|
||||||
public class SlipstreamCLI {
|
public class SlipstreamCLI {
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( SlipstreamCLI.class );
|
private static final Logger log = LoggerFactory.getLogger( SlipstreamCLI.class );
|
||||||
|
|
||||||
private static File backupDir = new File( "./backup/" );
|
private static final File backupDir = new File( "./backup/" );
|
||||||
private static File modsDir = new File( "./mods/" );
|
private static final File modsDir = new File( "./mods/" );
|
||||||
|
|
||||||
private static Thread.UncaughtExceptionHandler exceptionHandler = null;
|
private static Thread.UncaughtExceptionHandler exceptionHandler = null;
|
||||||
|
|
||||||
public static void main( String[] args ) {
|
public static void main( String[] args ) {
|
||||||
|
|
||||||
exceptionHandler = new Thread.UncaughtExceptionHandler() {
|
exceptionHandler = (t, e) -> {
|
||||||
@Override
|
log.error("Uncaught exception in thread: {}", t.toString(), e);
|
||||||
public void uncaughtException( Thread t, Throwable e ) {
|
|
||||||
log.error( "Uncaught exception in thread: "+ t.toString(), e );
|
|
||||||
System.exit( 1 );
|
System.exit( 1 );
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SlipstreamCommand slipstreamCmd = new SlipstreamCommand();
|
SlipstreamCommand slipstreamCmd = new SlipstreamCommand();
|
||||||
|
@ -149,7 +141,13 @@ public class SlipstreamCLI {
|
||||||
throw new FileNotFoundException( String.format( "Could not find either \"%s\" or both \"%s\" and \"%s\"", ftlDatFile.getName(), dataDatFile.getName(), resourceDatFile.getName() ) );
|
throw new FileNotFoundException( String.format( "Could not find either \"%s\" or both \"%s\" and \"%s\"", ftlDatFile.getName(), dataDatFile.getName(), resourceDatFile.getName() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !extractDir.exists() ) extractDir.mkdirs();
|
if ( !extractDir.exists() ) {
|
||||||
|
boolean success = extractDir.mkdirs();
|
||||||
|
if (success) {
|
||||||
|
log.error( "Error extracting dats");
|
||||||
|
System.exit( 1 );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
dstPack = new FolderPack( extractDir );
|
dstPack = new FolderPack( extractDir );
|
||||||
|
|
||||||
|
@ -173,14 +171,14 @@ public class SlipstreamCLI {
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
try {if ( is != null ) is.close();}
|
try {if ( is != null ) is.close();}
|
||||||
catch ( IOException ex ) {}
|
catch ( IOException ignored) {}
|
||||||
|
|
||||||
try {if ( dstPack != null ) dstPack.close();}
|
try {if ( dstPack != null ) dstPack.close();}
|
||||||
catch ( IOException ex ) {}
|
catch ( IOException ignored) {}
|
||||||
|
|
||||||
for ( AbstractPack pack : srcPacks ) {
|
for ( AbstractPack pack : srcPacks ) {
|
||||||
try {pack.close();}
|
try {pack.close();}
|
||||||
catch ( IOException ex ) {}
|
catch ( IOException ignored) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -197,13 +195,13 @@ public class SlipstreamCLI {
|
||||||
File modFile = new File( modsDir, modFileName );
|
File modFile = new File( modsDir, modFileName );
|
||||||
|
|
||||||
if ( modFile.isDirectory() ) {
|
if ( modFile.isDirectory() ) {
|
||||||
log.info( String.format( "Zipping dir: %s/", modFile.getName() ) );
|
log.info("Zipping dir: {}/", modFile.getName());
|
||||||
try {
|
try {
|
||||||
modFile = createTempMod( modFile );
|
modFile = createTempMod( modFile );
|
||||||
deleteHook.addDoomedFile( modFile );
|
deleteHook.addDoomedFile( modFile );
|
||||||
}
|
}
|
||||||
catch ( IOException e ) {
|
catch ( IOException e ) {
|
||||||
log.error( String.format( "Error zipping dir: %s/", modFile.getName() ), e );
|
log.error("Error zipping dir: {}/", modFile.getName(), e);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -216,17 +214,16 @@ public class SlipstreamCLI {
|
||||||
|
|
||||||
SilentPatchObserver patchObserver = new SilentPatchObserver();
|
SilentPatchObserver patchObserver = new SilentPatchObserver();
|
||||||
ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver );
|
ModPatchThread patchThread = new ModPatchThread( modFiles, datsDir, backupDir, globalPanic, patchObserver );
|
||||||
patchThread.setDefaultUncaughtExceptionHandler( exceptionHandler );
|
Thread.setDefaultUncaughtExceptionHandler( exceptionHandler );
|
||||||
deleteHook.addWatchedThread( patchThread );
|
deleteHook.addWatchedThread( patchThread );
|
||||||
|
|
||||||
patchThread.start();
|
patchThread.start();
|
||||||
while ( patchThread.isAlive() ) {
|
while ( patchThread.isAlive() ) {
|
||||||
try {patchThread.join();}
|
try {patchThread.join();}
|
||||||
catch ( InterruptedException e ) {}
|
catch ( InterruptedException ignored) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !patchObserver.hasSucceeded() ) return false;
|
return patchObserver.hasSucceeded();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean validate(String[] modFileNames) {
|
private static boolean validate(String[] modFileNames) {
|
||||||
|
@ -243,13 +240,13 @@ public class SlipstreamCLI {
|
||||||
File modFile = new File( modsDir, modFileName );
|
File modFile = new File( modsDir, modFileName );
|
||||||
|
|
||||||
if ( modFile.isDirectory() ) {
|
if ( modFile.isDirectory() ) {
|
||||||
log.info( String.format( "Zipping dir: %s/", modFile.getName() ) );
|
log.info("Zipping dir: {}/", modFile.getName());
|
||||||
try {
|
try {
|
||||||
modFile = createTempMod( modFile );
|
modFile = createTempMod( modFile );
|
||||||
deleteHook.addDoomedFile( modFile );
|
deleteHook.addDoomedFile( modFile );
|
||||||
}
|
}
|
||||||
catch ( IOException e ) {
|
catch ( IOException e ) {
|
||||||
log.error( String.format( "Error zipping dir: %s/", modFile.getName() ), e );
|
log.error("Error zipping dir: {}/", modFile.getName(), e);
|
||||||
|
|
||||||
List<ReportMessage> tmpMessages = new ArrayList<ReportMessage>();
|
List<ReportMessage> tmpMessages = new ArrayList<ReportMessage>();
|
||||||
tmpMessages.add( new ReportMessage( ReportMessage.SECTION, modFileName ) );
|
tmpMessages.add( new ReportMessage( ReportMessage.SECTION, modFileName ) );
|
||||||
|
@ -268,9 +265,9 @@ public class SlipstreamCLI {
|
||||||
formatter.format( validateReport.messages, resultBuf, 0 );
|
formatter.format( validateReport.messages, resultBuf, 0 );
|
||||||
resultBuf.append( "\n" );
|
resultBuf.append( "\n" );
|
||||||
|
|
||||||
if ( validateReport.outcome == false ) anyInvalid = true;
|
if (!validateReport.outcome) anyInvalid = true;
|
||||||
}
|
}
|
||||||
if ( resultBuf.length() == 0 ) {
|
if (resultBuf.isEmpty()) {
|
||||||
resultBuf.append( "No mods were checked." );
|
resultBuf.append( "No mods were checked." );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -284,8 +281,9 @@ public class SlipstreamCLI {
|
||||||
|
|
||||||
boolean allowZip = appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ).equals( "true" );
|
boolean allowZip = appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ).equals( "true" );
|
||||||
File[] modFiles = modsDir.listFiles( new ModAndDirFileFilter( allowZip, true ) );
|
File[] modFiles = modsDir.listFiles( new ModAndDirFileFilter( allowZip, true ) );
|
||||||
List<String> dirList = new ArrayList<String>();
|
List<String> dirList = new ArrayList<>();
|
||||||
List<String> fileList = new ArrayList<String>();
|
List<String> fileList = new ArrayList<>();
|
||||||
|
assert modFiles != null;
|
||||||
for ( File f : modFiles ) {
|
for ( File f : modFiles ) {
|
||||||
if ( f.isDirectory() )
|
if ( f.isDirectory() )
|
||||||
dirList.add( f.getName() +"/" );
|
dirList.add( f.getName() +"/" );
|
||||||
|
@ -308,7 +306,7 @@ public class SlipstreamCLI {
|
||||||
if ( "true".equals( appConfig.getProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" ) ) ) {
|
if ( "true".equals( appConfig.getProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" ) ) ) {
|
||||||
|
|
||||||
String steamPath = appConfig.getProperty( SlipstreamConfig.STEAM_EXE_PATH );
|
String steamPath = appConfig.getProperty( SlipstreamConfig.STEAM_EXE_PATH );
|
||||||
if ( steamPath.length() > 0 ) {
|
if (!steamPath.isEmpty()) {
|
||||||
exeFile = new File( steamPath );
|
exeFile = new File( steamPath );
|
||||||
|
|
||||||
if ( exeFile.exists() ) {
|
if ( exeFile.exists() ) {
|
||||||
|
@ -361,10 +359,10 @@ public class SlipstreamCLI {
|
||||||
File datsDir = null;
|
File datsDir = null;
|
||||||
String datsPath = appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH, "" );
|
String datsPath = appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH, "" );
|
||||||
|
|
||||||
if ( datsPath.length() > 0 ) {
|
if (!datsPath.isEmpty()) {
|
||||||
log.info( "Using FTL dats path from config: "+ datsPath );
|
log.info( "Using FTL dats path from config: "+ datsPath );
|
||||||
datsDir = new File( datsPath );
|
datsDir = new File( datsPath );
|
||||||
if ( FTLUtilities.isDatsDirValid( datsDir ) == false ) {
|
if (!FTLUtilities.isDatsDirValid(datsDir)) {
|
||||||
log.error( "The config's "+ SlipstreamConfig.FTL_DATS_PATH +" does not exist, or it is invalid" );
|
log.error( "The config's "+ SlipstreamConfig.FTL_DATS_PATH +" does not exist, or it is invalid" );
|
||||||
datsDir = null;
|
datsDir = null;
|
||||||
}
|
}
|
||||||
|
@ -383,23 +381,17 @@ public class SlipstreamCLI {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a temporary zip made from a directory.
|
* Returns a temporary zip made from a directory.
|
||||||
*
|
|
||||||
* Empty subdirs will be omitted.
|
* Empty subdirs will be omitted.
|
||||||
* The archive will be not be deleted on exit (handle that elsewhere).
|
* The archive will be not be deleted on exit (handle that elsewhere).
|
||||||
*/
|
*/
|
||||||
private static File createTempMod( File dir ) throws IOException {
|
private static File createTempMod( File dir ) throws IOException {
|
||||||
File tempFile = File.createTempFile( dir.getName() +"_temp-", ".zip" );
|
File tempFile = File.createTempFile( dir.getName() +"_temp-", ".zip" );
|
||||||
|
|
||||||
FileOutputStream fos = null;
|
try (FileOutputStream fos = new FileOutputStream(tempFile)) {
|
||||||
try {
|
|
||||||
fos = new FileOutputStream( tempFile );
|
|
||||||
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos));
|
ZipOutputStream zos = new ZipOutputStream(new BufferedOutputStream(fos));
|
||||||
addDirToArchive(zos, dir, null);
|
addDirToArchive(zos, dir, null);
|
||||||
zos.close();
|
zos.close();
|
||||||
}
|
} catch (IOException ignored) {
|
||||||
finally {
|
|
||||||
try {if ( fos != null ) fos.close();}
|
|
||||||
catch ( IOException e ) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return tempFile;
|
return tempFile;
|
||||||
|
@ -408,15 +400,13 @@ public class SlipstreamCLI {
|
||||||
private static void addDirToArchive( ZipOutputStream zos, File dir, String pathPrefix ) throws IOException {
|
private static void addDirToArchive( ZipOutputStream zos, File dir, String pathPrefix ) throws IOException {
|
||||||
if ( pathPrefix == null ) pathPrefix = "";
|
if ( pathPrefix == null ) pathPrefix = "";
|
||||||
|
|
||||||
for ( File f : dir.listFiles() ) {
|
for ( File f : Objects.requireNonNull(dir.listFiles())) {
|
||||||
if ( f.isDirectory() ) {
|
if ( f.isDirectory() ) {
|
||||||
addDirToArchive( zos, f, pathPrefix + f.getName() +"/" );
|
addDirToArchive( zos, f, pathPrefix + f.getName() +"/" );
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileInputStream is = null;
|
try (FileInputStream is = new FileInputStream(f)) {
|
||||||
try {
|
|
||||||
is = new FileInputStream( f );
|
|
||||||
zos.putNextEntry(new ZipEntry(pathPrefix + f.getName()));
|
zos.putNextEntry(new ZipEntry(pathPrefix + f.getName()));
|
||||||
|
|
||||||
byte[] buf = new byte[4096];
|
byte[] buf = new byte[4096];
|
||||||
|
@ -426,10 +416,7 @@ public class SlipstreamCLI {
|
||||||
}
|
}
|
||||||
|
|
||||||
zos.closeEntry();
|
zos.closeEntry();
|
||||||
}
|
} catch (IOException ignored) {
|
||||||
finally {
|
|
||||||
try {if ( is != null ) is.close();}
|
|
||||||
catch ( IOException e ) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -527,8 +514,8 @@ public class SlipstreamCLI {
|
||||||
|
|
||||||
|
|
||||||
private static class ModAndDirFileFilter implements FileFilter {
|
private static class ModAndDirFileFilter implements FileFilter {
|
||||||
private boolean allowZip;
|
private final boolean allowZip;
|
||||||
private boolean allowDirs;
|
private final boolean allowDirs;
|
||||||
|
|
||||||
public ModAndDirFileFilter( boolean allowZip, boolean allowDirs ) {
|
public ModAndDirFileFilter( boolean allowZip, boolean allowDirs ) {
|
||||||
this.allowZip = allowZip;
|
this.allowZip = allowZip;
|
||||||
|
@ -542,7 +529,7 @@ public class SlipstreamCLI {
|
||||||
if ( f.getName().endsWith(".ftl") ) return true;
|
if ( f.getName().endsWith(".ftl") ) return true;
|
||||||
|
|
||||||
if ( allowZip ) {
|
if ( allowZip ) {
|
||||||
if ( f.getName().endsWith(".zip") ) return true;
|
return f.getName().endsWith(".zip");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,104 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.Toolkit;
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.Action;
|
|
||||||
import javax.swing.JMenuItem;
|
|
||||||
import javax.swing.JPopupMenu;
|
|
||||||
import javax.swing.text.JTextComponent;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Cut/Copy/Paste/SelectAll context menu for JTextComponents.
|
|
||||||
*
|
|
||||||
* Add this listener after any others. Any pressed/released listeners that want
|
|
||||||
* to preempt this menu should call e.consume().
|
|
||||||
*/
|
|
||||||
public class ClipboardMenuMouseListener extends MouseAdapter {
|
|
||||||
|
|
||||||
private JPopupMenu popup = new JPopupMenu();
|
|
||||||
|
|
||||||
private Action cutAction;
|
|
||||||
private Action copyAction;
|
|
||||||
private Action pasteAction;
|
|
||||||
private Action selectAllAction;
|
|
||||||
|
|
||||||
private JTextComponent textComponent = null;
|
|
||||||
|
|
||||||
|
|
||||||
public ClipboardMenuMouseListener() {
|
|
||||||
cutAction = new AbstractAction( "Cut" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent ae ) {
|
|
||||||
textComponent.cut();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
copyAction = new AbstractAction( "Copy" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent ae ) {
|
|
||||||
textComponent.copy();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
pasteAction = new AbstractAction( "Paste" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent ae ) {
|
|
||||||
textComponent.paste();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
selectAllAction = new AbstractAction( "Select All" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent ae ) {
|
|
||||||
textComponent.selectAll();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
popup.add( cutAction );
|
|
||||||
popup.add( copyAction );
|
|
||||||
popup.add( pasteAction );
|
|
||||||
popup.addSeparator();
|
|
||||||
popup.add( selectAllAction );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mousePressed( MouseEvent e ) {
|
|
||||||
if ( e.isConsumed() ) return;
|
|
||||||
if ( e.isPopupTrigger() ) showMenu( e );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased( MouseEvent e ) {
|
|
||||||
if ( e.isConsumed() ) return;
|
|
||||||
if ( e.isPopupTrigger() ) showMenu( e );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void showMenu( MouseEvent e ) {
|
|
||||||
if ( e.getSource() instanceof JTextComponent == false ) return;
|
|
||||||
|
|
||||||
textComponent = (JTextComponent)e.getSource();
|
|
||||||
textComponent.requestFocus();
|
|
||||||
|
|
||||||
boolean enabled = textComponent.isEnabled();
|
|
||||||
boolean editable = textComponent.isEditable();
|
|
||||||
boolean nonempty = !(textComponent.getText() == null || textComponent.getText().equals( "" ));
|
|
||||||
boolean marked = textComponent.getSelectedText() != null;
|
|
||||||
|
|
||||||
boolean pasteAvailable = Toolkit.getDefaultToolkit().getSystemClipboard().getContents( null ).isDataFlavorSupported( DataFlavor.stringFlavor );
|
|
||||||
|
|
||||||
cutAction.setEnabled( enabled && editable && marked );
|
|
||||||
copyAction.setEnabled( enabled && marked );
|
|
||||||
pasteAction.setEnabled( enabled && editable && pasteAvailable );
|
|
||||||
selectAllAction.setEnabled( enabled && nonempty );
|
|
||||||
|
|
||||||
int nx = e.getX();
|
|
||||||
if ( nx > 500 ) nx = nx - popup.getSize().width;
|
|
||||||
|
|
||||||
popup.show( e.getComponent(), nx, e.getY() - popup.getSize().height );
|
|
||||||
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,213 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Desktop;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.Frame;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.Box;
|
|
||||||
import javax.swing.BoxLayout;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JDialog;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.ScrollPaneConstants;
|
|
||||||
import javax.swing.event.AncestorEvent;
|
|
||||||
import javax.swing.event.AncestorListener;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.FieldEditorPanel;
|
|
||||||
import net.vhati.modmanager.ui.FieldEditorPanel.ContentType;
|
|
||||||
import net.vhati.modmanager.ui.RegexDocument;
|
|
||||||
import net.vhati.modmanager.xml.JDOMModMetadataWriter;
|
|
||||||
|
|
||||||
|
|
||||||
public class CreateModDialog extends JDialog implements ActionListener {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( CreateModDialog.class );
|
|
||||||
|
|
||||||
protected static final String DIR_NAME = "Directory Name";
|
|
||||||
protected static final String AUDIO_ROOT = "audio/";
|
|
||||||
protected static final String DATA_ROOT = "data/";
|
|
||||||
protected static final String FONTS_ROOT = "fonts/";
|
|
||||||
protected static final String IMG_ROOT = "img/";
|
|
||||||
protected static final String XML_COMMENTS = "XML Comments";
|
|
||||||
protected static final String TITLE = "Title";
|
|
||||||
protected static final String URL = "Thread URL";
|
|
||||||
protected static final String AUTHOR = "Author";
|
|
||||||
protected static final String VERSION = "Version";
|
|
||||||
protected static final String DESC = "Description";
|
|
||||||
|
|
||||||
protected FieldEditorPanel editorPanel;
|
|
||||||
protected JButton applyBtn;
|
|
||||||
|
|
||||||
protected File modsDir;
|
|
||||||
|
|
||||||
|
|
||||||
public CreateModDialog( Frame owner, File modsDir ) {
|
|
||||||
super( owner, "New Mod" );
|
|
||||||
this.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
|
|
||||||
|
|
||||||
this.modsDir = modsDir;
|
|
||||||
|
|
||||||
editorPanel = new FieldEditorPanel( false );
|
|
||||||
editorPanel.setBorder( BorderFactory.createEmptyBorder( 10, 10, 0, 10 ) );
|
|
||||||
editorPanel.setNameWidth( 100 );
|
|
||||||
editorPanel.setValueWidth( 350 );
|
|
||||||
|
|
||||||
editorPanel.addRow( DIR_NAME, ContentType.STRING );
|
|
||||||
editorPanel.getString( DIR_NAME ).setDocument( new RegexDocument( "[^\\/:;*?<>|^\"]*" ) );
|
|
||||||
editorPanel.addTextRow( String.format( "The name of a directory to create in the %s/ folder.", modsDir.getName() ) );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( AUDIO_ROOT, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addRow( DATA_ROOT, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addRow( FONTS_ROOT, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addRow( IMG_ROOT, ContentType.BOOLEAN );
|
|
||||||
editorPanel.getBoolean( AUDIO_ROOT ).setHorizontalAlignment( javax.swing.SwingConstants.LEFT );
|
|
||||||
editorPanel.getBoolean( DATA_ROOT ).setHorizontalAlignment( javax.swing.SwingConstants.LEFT );
|
|
||||||
editorPanel.getBoolean( FONTS_ROOT ).setHorizontalAlignment( javax.swing.SwingConstants.LEFT );
|
|
||||||
editorPanel.getBoolean( IMG_ROOT ).setHorizontalAlignment( javax.swing.SwingConstants.LEFT );
|
|
||||||
editorPanel.addTextRow( "Create empty top-level directories?" );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( XML_COMMENTS, ContentType.BOOLEAN );
|
|
||||||
editorPanel.getBoolean( XML_COMMENTS ).setHorizontalAlignment( javax.swing.SwingConstants.LEFT );
|
|
||||||
editorPanel.addTextRow( "Include XML comments about the purpose of these fields?" );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( TITLE, ContentType.STRING );
|
|
||||||
editorPanel.addTextRow( "The title of this mod." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( URL, ContentType.STRING );
|
|
||||||
editorPanel.addTextRow( "This mod's thread on the forum." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( AUTHOR, ContentType.STRING );
|
|
||||||
editorPanel.addTextRow( "Your forum user name." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( VERSION, ContentType.STRING );
|
|
||||||
editorPanel.addTextRow( "The revision/variant of this release, preferably at least a number." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( DESC, ContentType.TEXT_AREA );
|
|
||||||
editorPanel.getTextArea( DESC ).setRows( 15 );
|
|
||||||
editorPanel.addTextRow( "Summary of gameplay effects; flavor; features; concerns about compatibility, recommended patch order, requirements; replaced ship slot; etc." );
|
|
||||||
|
|
||||||
editorPanel.getBoolean( XML_COMMENTS ).setSelected( true );
|
|
||||||
|
|
||||||
JPanel ctrlPanel = new JPanel();
|
|
||||||
ctrlPanel.setLayout( new BoxLayout( ctrlPanel, BoxLayout.X_AXIS ) );
|
|
||||||
ctrlPanel.setBorder( BorderFactory.createEmptyBorder( 10, 0, 10, 0 ) );
|
|
||||||
ctrlPanel.add( Box.createHorizontalGlue() );
|
|
||||||
applyBtn = new JButton( "Generate Mod" );
|
|
||||||
applyBtn.addActionListener( this );
|
|
||||||
ctrlPanel.add( applyBtn );
|
|
||||||
ctrlPanel.add( Box.createHorizontalGlue() );
|
|
||||||
|
|
||||||
final JScrollPane editorScroll = new JScrollPane( editorPanel );
|
|
||||||
editorScroll.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS );
|
|
||||||
editorScroll.getVerticalScrollBar().setUnitIncrement( 10 );
|
|
||||||
int vbarWidth = editorScroll.getVerticalScrollBar().getPreferredSize().width;
|
|
||||||
editorScroll.setPreferredSize( new Dimension( editorPanel.getPreferredSize().width+vbarWidth+5, 400 ) );
|
|
||||||
|
|
||||||
JPanel contentPane = new JPanel( new BorderLayout() );
|
|
||||||
contentPane.add( editorScroll, BorderLayout.CENTER );
|
|
||||||
contentPane.add( ctrlPanel, BorderLayout.SOUTH );
|
|
||||||
this.setContentPane( contentPane );
|
|
||||||
this.pack();
|
|
||||||
this.setMinimumSize( new Dimension( 250, 250 ) );
|
|
||||||
|
|
||||||
|
|
||||||
editorScroll.addAncestorListener(new AncestorListener() {
|
|
||||||
@Override
|
|
||||||
public void ancestorAdded( AncestorEvent e ) {
|
|
||||||
editorScroll.getViewport().setViewPosition( new Point( 0, 0 ) );
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorMoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorRemoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
Object source = e.getSource();
|
|
||||||
|
|
||||||
if ( source == applyBtn ) {
|
|
||||||
String dirName = editorPanel.getString( DIR_NAME ).getText().trim();
|
|
||||||
String modTitle = editorPanel.getString( TITLE ).getText().trim();
|
|
||||||
String modURL = editorPanel.getString( URL ).getText().trim();
|
|
||||||
String modAuthor = editorPanel.getString( AUTHOR ).getText().trim();
|
|
||||||
String modVersion = editorPanel.getString( VERSION ).getText().trim();
|
|
||||||
String modDesc = editorPanel.getTextArea( DESC ).getText().trim();
|
|
||||||
boolean xmlComments = editorPanel.getBoolean( XML_COMMENTS ).isSelected();
|
|
||||||
|
|
||||||
if ( dirName.length() == 0 ) {
|
|
||||||
JOptionPane.showMessageDialog( CreateModDialog.this, "No directory name was given.", "Nothing to do", JOptionPane.WARNING_MESSAGE );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
File genDir = new File( modsDir, dirName );
|
|
||||||
if ( !genDir.exists() ) {
|
|
||||||
try {
|
|
||||||
// Generate the mod.
|
|
||||||
if ( genDir.mkdir() ) {
|
|
||||||
File appendixDir = new File ( genDir, "mod-appendix" );
|
|
||||||
if ( appendixDir.mkdir() ) {
|
|
||||||
File metadataFile = new File( appendixDir, "metadata.xml" );
|
|
||||||
|
|
||||||
JDOMModMetadataWriter.writeMetadata( metadataFile, modTitle, modURL, modAuthor, modVersion, modDesc, xmlComments );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IOException( String.format( "Failed to create directory: %s", appendixDir.getName() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IOException( String.format( "Failed to create directory: %s", genDir.getName() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create root dirs.
|
|
||||||
if ( editorPanel.getBoolean( AUDIO_ROOT ).isSelected() )
|
|
||||||
new File( genDir, "audio" ).mkdir();
|
|
||||||
if ( editorPanel.getBoolean( DATA_ROOT ).isSelected() )
|
|
||||||
new File( genDir, "data" ).mkdir();
|
|
||||||
if ( editorPanel.getBoolean( FONTS_ROOT ).isSelected() )
|
|
||||||
new File( genDir, "fonts" ).mkdir();
|
|
||||||
if ( editorPanel.getBoolean( IMG_ROOT ).isSelected() )
|
|
||||||
new File( genDir, "img" ).mkdir();
|
|
||||||
|
|
||||||
// Show the folder.
|
|
||||||
try {
|
|
||||||
if ( Desktop.isDesktopSupported() ) {
|
|
||||||
Desktop.getDesktop().open( genDir.getCanonicalFile() );
|
|
||||||
} else {
|
|
||||||
log.error( String.format( "Java cannot open the %s/ folder for you on this OS", genDir.getName() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( IOException f ) {
|
|
||||||
log.error( String.format( "Error opening %s/ folder", genDir.getName() ), f );
|
|
||||||
}
|
|
||||||
|
|
||||||
// All done.
|
|
||||||
CreateModDialog.this.dispose();
|
|
||||||
}
|
|
||||||
catch ( IOException f ) {
|
|
||||||
log.error( String.format( "Failed to generate new mod: %s", genDir.getName() ), f );
|
|
||||||
|
|
||||||
JOptionPane.showMessageDialog( CreateModDialog.this, String.format( "Failed to generate new mod: %s\n%s", genDir.getName(), f.getMessage() ), "Error", JOptionPane.ERROR_MESSAGE );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
JOptionPane.showMessageDialog( CreateModDialog.this, String.format( "A directory named \"%s\" already exists.", genDir.getName() ), "Nothing to do", JOptionPane.WARNING_MESSAGE );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,163 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.Frame;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.swing.JDialog;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import net.vhati.ftldat.AbstractPack;
|
|
||||||
import net.vhati.ftldat.FolderPack;
|
|
||||||
import net.vhati.ftldat.FTLPack;
|
|
||||||
import net.vhati.ftldat.PkgPack;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.ProgressDialog;
|
|
||||||
|
|
||||||
|
|
||||||
public class DatExtractDialog extends ProgressDialog {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( DatExtractDialog.class );
|
|
||||||
|
|
||||||
private boolean started = false;
|
|
||||||
|
|
||||||
private File extractDir = null;
|
|
||||||
private File datsDir = null;
|
|
||||||
|
|
||||||
private DatExtractThread workerThread = null;
|
|
||||||
|
|
||||||
|
|
||||||
public DatExtractDialog( Frame owner, File extractDir, File datsDir ) {
|
|
||||||
super( owner, false );
|
|
||||||
this.setTitle( "Extracting..." );
|
|
||||||
|
|
||||||
this.extractDir = extractDir;
|
|
||||||
this.datsDir = datsDir;
|
|
||||||
|
|
||||||
this.setSize( 400, 160 );
|
|
||||||
this.setMinimumSize( this.getPreferredSize() );
|
|
||||||
this.setLocationRelativeTo( owner );
|
|
||||||
|
|
||||||
workerThread = new DatExtractThread( extractDir, datsDir );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the worker thread that does the extracting.
|
|
||||||
*
|
|
||||||
* This method is provided so other classes can customize the thread
|
|
||||||
* before calling extract().
|
|
||||||
*/
|
|
||||||
public Thread getWorkerThread() {
|
|
||||||
return workerThread;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the background extraction thread.
|
|
||||||
* Call this immediately before setVisible().
|
|
||||||
*/
|
|
||||||
public void extract() {
|
|
||||||
if ( started ) return;
|
|
||||||
|
|
||||||
workerThread.start();
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setTaskOutcome( boolean outcome, Exception e ) {
|
|
||||||
super.setTaskOutcome( outcome, e );
|
|
||||||
if ( !this.isShowing() ) return;
|
|
||||||
|
|
||||||
if ( succeeded ) {
|
|
||||||
setStatusText( "All resources extracted successfully." );
|
|
||||||
} else {
|
|
||||||
setStatusText( String.format( "Error extracting dats: %s", e ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private class DatExtractThread extends Thread {
|
|
||||||
|
|
||||||
private File extractDir = null;
|
|
||||||
private File datsDir = null;
|
|
||||||
|
|
||||||
|
|
||||||
public DatExtractThread( File extractDir, File datsDir ) {
|
|
||||||
this.extractDir = extractDir;
|
|
||||||
this.datsDir = datsDir;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
AbstractPack dstPack = null;
|
|
||||||
List<AbstractPack> srcPacks = new ArrayList<AbstractPack>( 2 );
|
|
||||||
InputStream is = null;
|
|
||||||
int progress = 0;
|
|
||||||
|
|
||||||
try {
|
|
||||||
File ftlDatFile = new File( datsDir, "ftl.dat" );
|
|
||||||
File dataDatFile = new File( datsDir, "data.dat" );
|
|
||||||
File resourceDatFile = new File( datsDir, "resource.dat" );
|
|
||||||
|
|
||||||
if ( ftlDatFile.exists() ) { // FTL 1.6.1.
|
|
||||||
AbstractPack ftlPack = new PkgPack( ftlDatFile, "r" );
|
|
||||||
srcPacks.add( ftlPack );
|
|
||||||
}
|
|
||||||
else if ( dataDatFile.exists() && resourceDatFile.exists() ) { // FTL 1.01-1.5.13.
|
|
||||||
AbstractPack dataPack = new FTLPack( dataDatFile, "r" );
|
|
||||||
AbstractPack resourcePack = new FTLPack( resourceDatFile, "r" );
|
|
||||||
srcPacks.add( dataPack );
|
|
||||||
srcPacks.add( resourcePack );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new FileNotFoundException( String.format( "Could not find either \"%s\" or both \"%s\" and \"%s\"", ftlDatFile.getName(), dataDatFile.getName(), resourceDatFile.getName() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !extractDir.exists() ) extractDir.mkdirs();
|
|
||||||
|
|
||||||
dstPack = new FolderPack( extractDir );
|
|
||||||
|
|
||||||
for ( AbstractPack srcPack : srcPacks ) {
|
|
||||||
progress = 0;
|
|
||||||
List<String> innerPaths = srcPack.list();
|
|
||||||
setProgressLater( progress, innerPaths.size() );
|
|
||||||
|
|
||||||
for ( String innerPath : innerPaths ) {
|
|
||||||
setStatusTextLater( innerPath );
|
|
||||||
if ( dstPack.contains( innerPath ) ) {
|
|
||||||
log.info( "While extracting resources, this file was overwritten: "+ innerPath );
|
|
||||||
dstPack.remove( innerPath );
|
|
||||||
}
|
|
||||||
is = srcPack.getInputStream( innerPath );
|
|
||||||
dstPack.add( innerPath, is );
|
|
||||||
setProgressLater( progress++ );
|
|
||||||
}
|
|
||||||
srcPack.close();
|
|
||||||
}
|
|
||||||
setTaskOutcomeLater( true, null );
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
log.error( "Error extracting dats", e );
|
|
||||||
setTaskOutcomeLater( false, e );
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {if ( is != null ) is.close();}
|
|
||||||
catch ( IOException e ) {}
|
|
||||||
|
|
||||||
try {if ( dstPack != null ) dstPack.close();}
|
|
||||||
catch ( IOException e ) {}
|
|
||||||
|
|
||||||
for ( AbstractPack pack : srcPacks ) {
|
|
||||||
try {pack.close();}
|
|
||||||
catch ( IOException ex ) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,457 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.GridBagConstraints;
|
|
||||||
import java.awt.GridBagLayout;
|
|
||||||
import java.awt.Insets;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.Box;
|
|
||||||
import javax.swing.BoxLayout;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JCheckBox;
|
|
||||||
import javax.swing.JComboBox;
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JSeparator;
|
|
||||||
import javax.swing.JSlider;
|
|
||||||
import javax.swing.JTextArea;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.SwingConstants;
|
|
||||||
import javax.swing.UIManager;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.RegexDocument;
|
|
||||||
|
|
||||||
|
|
||||||
public class FieldEditorPanel extends JPanel {
|
|
||||||
public enum ContentType { WRAPPED_LABEL, LABEL, STRING, TEXT_AREA, INTEGER, BOOLEAN, SLIDER, COMBO, CHOOSER }
|
|
||||||
|
|
||||||
private Map<String, JTextArea> wrappedLabelMap = new HashMap<String, JTextArea>();
|
|
||||||
private Map<String, JLabel> labelMap = new HashMap<String, JLabel>();
|
|
||||||
private Map<String, JTextField> stringMap = new HashMap<String, JTextField>();
|
|
||||||
private Map<String, JTextArea> textAreaMap = new HashMap<String, JTextArea>();
|
|
||||||
private Map<String, JTextField> intMap = new HashMap<String, JTextField>();
|
|
||||||
private Map<String, JCheckBox> boolMap = new HashMap<String, JCheckBox>();
|
|
||||||
private Map<String, JSlider> sliderMap = new HashMap<String, JSlider>();
|
|
||||||
private Map<String, JComboBox> comboMap = new HashMap<String, JComboBox>();
|
|
||||||
private Map<String, Chooser> chooserMap = new HashMap<String, Chooser>();
|
|
||||||
private Map<String, JLabel> reminderMap = new HashMap<String, JLabel>();
|
|
||||||
|
|
||||||
private GridBagConstraints gridC = new GridBagConstraints();
|
|
||||||
|
|
||||||
private Component nameStrut = Box.createHorizontalStrut( 1 );
|
|
||||||
private Component valueStrut = Box.createHorizontalStrut( 120 );
|
|
||||||
private Component reminderStrut = Box.createHorizontalStrut( 90 );
|
|
||||||
|
|
||||||
private boolean remindersVisible;
|
|
||||||
|
|
||||||
|
|
||||||
public FieldEditorPanel( boolean remindersVisible ) {
|
|
||||||
super( new GridBagLayout() );
|
|
||||||
this.remindersVisible = remindersVisible;
|
|
||||||
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
gridC.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
gridC.weightx = 0.0;
|
|
||||||
gridC.weighty = 0.0;
|
|
||||||
gridC.gridwidth = 1;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
gridC.gridy = 0;
|
|
||||||
|
|
||||||
// No default width for col 0.
|
|
||||||
gridC.gridx = 0;
|
|
||||||
this.add( nameStrut, gridC );
|
|
||||||
gridC.gridx++;
|
|
||||||
this.add( valueStrut, gridC );
|
|
||||||
gridC.gridx++;
|
|
||||||
if ( remindersVisible ) {
|
|
||||||
this.add( reminderStrut, gridC );
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
gridC.insets = new Insets( 2, 4, 2, 4 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setNameWidth( int width ) {
|
|
||||||
nameStrut.setMinimumSize( new Dimension( width, 0 ) );
|
|
||||||
nameStrut.setPreferredSize( new Dimension( width, 0 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValueWidth( int width ) {
|
|
||||||
valueStrut.setMinimumSize( new Dimension( width, 0 ) );
|
|
||||||
valueStrut.setPreferredSize( new Dimension( width, 0 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReminderWidth( int width ) {
|
|
||||||
reminderStrut.setMinimumSize( new Dimension( width, 0 ) );
|
|
||||||
reminderStrut.setPreferredSize( new Dimension( width, 0 ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs JComponents for a given type of value.
|
|
||||||
* A row consists of a static label, some JComponent,
|
|
||||||
* and a reminder label.
|
|
||||||
*
|
|
||||||
* The component and reminder will be accessable later
|
|
||||||
* via getter methods.
|
|
||||||
*/
|
|
||||||
public void addRow( String valueName, ContentType contentType ) {
|
|
||||||
gridC.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
gridC.gridwidth = 1;
|
|
||||||
gridC.weighty = 0.0;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
this.add( new JLabel( valueName +":" ), gridC );
|
|
||||||
|
|
||||||
gridC.gridx++;
|
|
||||||
if ( contentType == ContentType.WRAPPED_LABEL ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JTextArea valueArea = new JTextArea();
|
|
||||||
valueArea.setBackground( null );
|
|
||||||
valueArea.setEditable( false );
|
|
||||||
valueArea.setBorder( null );
|
|
||||||
valueArea.setLineWrap( true );
|
|
||||||
valueArea.setWrapStyleWord( true );
|
|
||||||
valueArea.setFocusable( false );
|
|
||||||
valueArea.setFont( UIManager.getFont( "Label.font" ) );
|
|
||||||
|
|
||||||
wrappedLabelMap.put( valueName, valueArea );
|
|
||||||
this.add( valueArea, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.LABEL ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JLabel valueLbl = new JLabel();
|
|
||||||
valueLbl.setHorizontalAlignment( SwingConstants.CENTER );
|
|
||||||
labelMap.put( valueName, valueLbl );
|
|
||||||
this.add( valueLbl, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.STRING ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JTextField valueField = new JTextField();
|
|
||||||
stringMap.put( valueName, valueField );
|
|
||||||
this.add( valueField, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.TEXT_AREA ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JTextArea valueArea = new JTextArea();
|
|
||||||
valueArea.setEditable( true );
|
|
||||||
valueArea.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createEtchedBorder(), BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) ) );
|
|
||||||
valueArea.setLineWrap( true );
|
|
||||||
valueArea.setWrapStyleWord( true );
|
|
||||||
valueArea.setFocusable( true );
|
|
||||||
valueArea.setFont( UIManager.getFont( "TextField.font" ) ); // Override small default font on systemLaf.
|
|
||||||
|
|
||||||
textAreaMap.put( valueName, valueArea );
|
|
||||||
this.add( valueArea, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.INTEGER ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JTextField valueField = new JTextField();
|
|
||||||
valueField.setHorizontalAlignment( JTextField.RIGHT );
|
|
||||||
valueField.setDocument( new RegexDocument( "[0-9]*" ) );
|
|
||||||
intMap.put( valueName, valueField );
|
|
||||||
this.add( valueField, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.BOOLEAN ) {
|
|
||||||
gridC.anchor = GridBagConstraints.CENTER;
|
|
||||||
JCheckBox valueCheck = new JCheckBox();
|
|
||||||
valueCheck.setHorizontalAlignment( SwingConstants.CENTER );
|
|
||||||
boolMap.put( valueName, valueCheck );
|
|
||||||
this.add( valueCheck, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.SLIDER ) {
|
|
||||||
gridC.anchor = GridBagConstraints.CENTER;
|
|
||||||
JPanel panel = new JPanel();
|
|
||||||
panel.setLayout( new BoxLayout( panel, BoxLayout.X_AXIS ) );
|
|
||||||
final JSlider valueSlider = new JSlider( JSlider.HORIZONTAL );
|
|
||||||
valueSlider.setPreferredSize( new Dimension( 50, valueSlider.getPreferredSize().height ) );
|
|
||||||
sliderMap.put( valueName, valueSlider );
|
|
||||||
panel.add( valueSlider );
|
|
||||||
final JTextField valueField = new JTextField( 3 );
|
|
||||||
valueField.setMaximumSize( valueField.getPreferredSize() );
|
|
||||||
valueField.setHorizontalAlignment( JTextField.RIGHT );
|
|
||||||
valueField.setEditable( false );
|
|
||||||
panel.add( valueField );
|
|
||||||
this.add( panel, gridC );
|
|
||||||
|
|
||||||
valueSlider.addChangeListener(new ChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void stateChanged( ChangeEvent e ) {
|
|
||||||
valueField.setText( ""+valueSlider.getValue() );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.COMBO ) {
|
|
||||||
gridC.anchor = GridBagConstraints.CENTER;
|
|
||||||
JComboBox valueCombo = new JComboBox();
|
|
||||||
valueCombo.setEditable( false );
|
|
||||||
comboMap.put( valueName, valueCombo );
|
|
||||||
this.add( valueCombo, gridC );
|
|
||||||
}
|
|
||||||
else if ( contentType == ContentType.CHOOSER ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JPanel panel = new JPanel();
|
|
||||||
panel.setLayout( new BoxLayout( panel, BoxLayout.X_AXIS ) );
|
|
||||||
|
|
||||||
JTextField chooserField = new JTextField();
|
|
||||||
panel.add( chooserField );
|
|
||||||
panel.add( Box.createHorizontalStrut( 5 ) );
|
|
||||||
JButton chooserBtn = new JButton( "..." );
|
|
||||||
chooserBtn.setMargin( new Insets( 1,2,1,2 ) );
|
|
||||||
panel.add( chooserBtn );
|
|
||||||
Chooser valueChooser = new Chooser( chooserField, chooserBtn );
|
|
||||||
chooserMap.put( valueName, valueChooser );
|
|
||||||
|
|
||||||
this.add( panel, gridC );
|
|
||||||
}
|
|
||||||
gridC.gridx++;
|
|
||||||
|
|
||||||
if ( remindersVisible ) {
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JLabel valueReminder = new JLabel();
|
|
||||||
reminderMap.put( valueName, valueReminder );
|
|
||||||
this.add( valueReminder, gridC );
|
|
||||||
}
|
|
||||||
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addTextRow( String text ) {
|
|
||||||
gridC.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
gridC.weighty = 0.0;
|
|
||||||
gridC.gridwidth = GridBagConstraints.REMAINDER;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
JTextArea textArea = new JTextArea( text );
|
|
||||||
textArea.setBackground( null );
|
|
||||||
textArea.setEditable( false );
|
|
||||||
textArea.setBorder( null );
|
|
||||||
textArea.setLineWrap( true );
|
|
||||||
textArea.setWrapStyleWord( true );
|
|
||||||
textArea.setFocusable( false );
|
|
||||||
textArea.setFont( UIManager.getFont( "Label.font" ) );
|
|
||||||
|
|
||||||
this.add( textArea, gridC );
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addSeparatorRow() {
|
|
||||||
gridC.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
gridC.weighty = 0.0;
|
|
||||||
gridC.gridwidth = GridBagConstraints.REMAINDER;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
|
|
||||||
JPanel panel = new JPanel();
|
|
||||||
panel.setLayout( new BoxLayout( panel, BoxLayout.Y_AXIS ) );
|
|
||||||
panel.add( Box.createVerticalStrut( 8 ) );
|
|
||||||
JSeparator sep = new JSeparator();
|
|
||||||
sep.setPreferredSize( new Dimension( 1, sep.getPreferredSize().height ) );
|
|
||||||
panel.add( sep );
|
|
||||||
panel.add( Box.createVerticalStrut( 8 ) );
|
|
||||||
|
|
||||||
this.add( panel, gridC );
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addBlankRow() {
|
|
||||||
gridC.fill = GridBagConstraints.NONE;
|
|
||||||
gridC.weighty = 0.0;
|
|
||||||
gridC.gridwidth = GridBagConstraints.REMAINDER;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
|
|
||||||
this.add( Box.createVerticalStrut( 12 ), gridC );
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addFillRow() {
|
|
||||||
gridC.fill = GridBagConstraints.VERTICAL;
|
|
||||||
gridC.weighty = 1.0;
|
|
||||||
gridC.gridwidth = GridBagConstraints.REMAINDER;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
|
|
||||||
this.add( Box.createVerticalGlue(), gridC );
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setStringAndReminder( String valueName, String s ) {
|
|
||||||
JTextField valueField = stringMap.get( valueName );
|
|
||||||
if ( valueField != null ) valueField.setText( s );
|
|
||||||
if ( remindersVisible ) setReminder( valueName, s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIntAndReminder( String valueName, int n ) {
|
|
||||||
setIntAndReminder( valueName, n, ""+n );
|
|
||||||
}
|
|
||||||
public void setIntAndReminder( String valueName, int n, String s ) {
|
|
||||||
JTextField valueField = intMap.get( valueName );
|
|
||||||
if ( valueField != null ) valueField.setText( ""+n );
|
|
||||||
if ( remindersVisible ) setReminder( valueName, s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBoolAndReminder( String valueName, boolean b ) {
|
|
||||||
setBoolAndReminder( valueName, b, ""+b );
|
|
||||||
}
|
|
||||||
public void setBoolAndReminder( String valueName, boolean b, String s ) {
|
|
||||||
JCheckBox valueCheck = boolMap.get( valueName );
|
|
||||||
if ( valueCheck != null ) valueCheck.setSelected( b );
|
|
||||||
if ( remindersVisible ) setReminder( valueName, s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSliderAndReminder( String valueName, int n ) {
|
|
||||||
setSliderAndReminder( valueName, n, ""+n );
|
|
||||||
}
|
|
||||||
public void setSliderAndReminder( String valueName, int n, String s ) {
|
|
||||||
JSlider valueSlider = sliderMap.get( valueName );
|
|
||||||
if ( valueSlider != null ) valueSlider.setValue( n );
|
|
||||||
if ( remindersVisible ) setReminder( valueName, s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setComboAndReminder( String valueName, Object o ) {
|
|
||||||
setComboAndReminder( valueName, o, o.toString() );
|
|
||||||
}
|
|
||||||
public void setComboAndReminder( String valueName, Object o, String s ) {
|
|
||||||
JComboBox valueCombo = comboMap.get( valueName );
|
|
||||||
if ( valueCombo != null ) valueCombo.setSelectedItem( o );
|
|
||||||
if ( remindersVisible ) setReminder( valueName, s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setChooserAndReminder( String valueName, String s ) {
|
|
||||||
Chooser valueChooser = chooserMap.get( valueName );
|
|
||||||
if ( valueChooser != null ) valueChooser.getTextField().setText( s );
|
|
||||||
if ( remindersVisible ) setReminder( valueName, s );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setReminder( String valueName, String s ) {
|
|
||||||
JLabel valueReminder = reminderMap.get( valueName );
|
|
||||||
if ( valueReminder != null ) valueReminder.setText( "( "+ s +" )" );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTextArea getWrappedLabel( String valueName ) {
|
|
||||||
return wrappedLabelMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JLabel getLabel( String valueName ) {
|
|
||||||
return labelMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTextField getString( String valueName ) {
|
|
||||||
return stringMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTextArea getTextArea( String valueName ) {
|
|
||||||
return textAreaMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTextField getInt( String valueName ) {
|
|
||||||
return intMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JCheckBox getBoolean( String valueName ) {
|
|
||||||
return boolMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSlider getSlider( String valueName ) {
|
|
||||||
return sliderMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public JComboBox getCombo( String valueName ) {
|
|
||||||
return comboMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Chooser getChooser( String valueName ) {
|
|
||||||
return chooserMap.get( valueName );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void reset() {
|
|
||||||
for ( JTextArea valueArea : wrappedLabelMap.values() )
|
|
||||||
valueArea.setText( "" );
|
|
||||||
|
|
||||||
for ( JLabel valueLbl : labelMap.values() )
|
|
||||||
valueLbl.setText( "" );
|
|
||||||
|
|
||||||
for ( JTextField valueField : stringMap.values() )
|
|
||||||
valueField.setText( "" );
|
|
||||||
|
|
||||||
for ( JTextArea valueArea : textAreaMap.values() )
|
|
||||||
valueArea.setText( "" );
|
|
||||||
|
|
||||||
for ( JTextField valueField : intMap.values() )
|
|
||||||
valueField.setText( "" );
|
|
||||||
|
|
||||||
for ( JCheckBox valueCheck : boolMap.values() )
|
|
||||||
valueCheck.setSelected( false );
|
|
||||||
|
|
||||||
for ( JSlider valueSlider : sliderMap.values() )
|
|
||||||
valueSlider.setValue( 0 );
|
|
||||||
|
|
||||||
for ( JComboBox valueCombo : comboMap.values() )
|
|
||||||
valueCombo.removeAllItems();
|
|
||||||
|
|
||||||
for ( Chooser valueChooser : chooserMap.values() )
|
|
||||||
valueChooser.getTextField().setText( "" );
|
|
||||||
|
|
||||||
for ( JLabel valueReminder : reminderMap.values() )
|
|
||||||
valueReminder.setText( "" );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeAll() {
|
|
||||||
wrappedLabelMap.clear();
|
|
||||||
labelMap.clear();
|
|
||||||
stringMap.clear();
|
|
||||||
textAreaMap.clear();
|
|
||||||
intMap.clear();
|
|
||||||
boolMap.clear();
|
|
||||||
sliderMap.clear();
|
|
||||||
comboMap.clear();
|
|
||||||
chooserMap.clear();
|
|
||||||
reminderMap.clear();
|
|
||||||
super.removeAll();
|
|
||||||
gridC = new GridBagConstraints();
|
|
||||||
|
|
||||||
gridC.anchor = GridBagConstraints.WEST;
|
|
||||||
gridC.fill = GridBagConstraints.HORIZONTAL;
|
|
||||||
gridC.weightx = 0.0;
|
|
||||||
gridC.weighty = 0.0;
|
|
||||||
gridC.gridwidth = 1;
|
|
||||||
gridC.gridx = 0;
|
|
||||||
gridC.gridy = 0;
|
|
||||||
|
|
||||||
// No default width for col 0.
|
|
||||||
gridC.gridx = 0;
|
|
||||||
this.add( Box.createVerticalStrut( 1 ), gridC );
|
|
||||||
gridC.gridx++;
|
|
||||||
this.add( valueStrut, gridC );
|
|
||||||
gridC.gridx++;
|
|
||||||
if ( remindersVisible ) {
|
|
||||||
this.add( reminderStrut, gridC );
|
|
||||||
gridC.gridy++;
|
|
||||||
}
|
|
||||||
|
|
||||||
gridC.insets = new Insets( 2, 4, 2, 4 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class Chooser {
|
|
||||||
private JTextField textField;
|
|
||||||
private JButton button;
|
|
||||||
|
|
||||||
public Chooser( JTextField textField, JButton button ) {
|
|
||||||
this.textField = textField;
|
|
||||||
this.button = button;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTextField getTextField() { return textField; }
|
|
||||||
public JButton getButton() { return button; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.KeyEventDispatcher;
|
|
||||||
import java.awt.KeyboardFocusManager;
|
|
||||||
import java.awt.Window;
|
|
||||||
import java.awt.event.KeyAdapter;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A panel that consumes all mouse/keyboard events, for use as a glass pane.
|
|
||||||
*/
|
|
||||||
public class InertPanel extends JPanel {
|
|
||||||
|
|
||||||
private KeyEventDispatcher nullDispatcher;
|
|
||||||
|
|
||||||
|
|
||||||
public InertPanel() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
nullDispatcher = new KeyEventDispatcher() {
|
|
||||||
@Override
|
|
||||||
public boolean dispatchKeyEvent( KeyEvent e ) {
|
|
||||||
Object source = e.getSource();
|
|
||||||
if ( source instanceof Component == false ) return false;
|
|
||||||
|
|
||||||
Window ancestor = SwingUtilities.getWindowAncestor( (Component)source );
|
|
||||||
if ( ancestor instanceof JFrame == false ) return false;
|
|
||||||
|
|
||||||
return ( InertPanel.this == ((JFrame)ancestor).getGlassPane() );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mousePressed( MouseEvent e ) {e.consume();}
|
|
||||||
@Override
|
|
||||||
public void mouseReleased( MouseEvent e ) {e.consume();}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.setCursor(Cursor.getPredefinedCursor( Cursor.WAIT_CURSOR ));
|
|
||||||
this.setOpaque( false );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVisible( boolean b ) {
|
|
||||||
super.setVisible( b );
|
|
||||||
if ( b ) {
|
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher( nullDispatcher );
|
|
||||||
} else {
|
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher( nullDispatcher );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,243 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.charset.Charset;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.locks.Lock;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.core.AutoUpdateInfo;
|
|
||||||
import net.vhati.modmanager.core.ModDB;
|
|
||||||
import net.vhati.modmanager.core.ModFileInfo;
|
|
||||||
import net.vhati.modmanager.core.SlipstreamConfig;
|
|
||||||
import net.vhati.modmanager.json.JacksonAutoUpdateReader;
|
|
||||||
import net.vhati.modmanager.json.JacksonCatalogReader;
|
|
||||||
import net.vhati.modmanager.json.URLFetcher;
|
|
||||||
import net.vhati.modmanager.ui.ManagerFrame;
|
|
||||||
import net.vhati.modmanager.ui.table.ListState;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs I/O-related setup for ManagerFrame in the background.
|
|
||||||
*
|
|
||||||
* Reads cached local metadata.
|
|
||||||
* Rescans the "mods/" folder.
|
|
||||||
* Reads saved catalog, and redownloads if stale.
|
|
||||||
* Reads saved info about app updates, and redownloads if stale.
|
|
||||||
*/
|
|
||||||
public class ManagerInitThread extends Thread {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( ManagerInitThread.class );
|
|
||||||
|
|
||||||
private final ManagerFrame frame;
|
|
||||||
private final SlipstreamConfig appConfig;
|
|
||||||
private final File modsDir;
|
|
||||||
private final File modsTableStateFile;
|
|
||||||
private final File metadataFile;
|
|
||||||
private final File catalogFile;
|
|
||||||
private final File catalogETagFile;
|
|
||||||
private final File appUpdateFile;
|
|
||||||
private final File appUpdateETagFile;
|
|
||||||
|
|
||||||
|
|
||||||
public ManagerInitThread( ManagerFrame frame, SlipstreamConfig appConfig, File modsDir, File modsTableStateFile, File metadataFile, File catalogFile, File catalogETagFile, File appUpdateFile, File appUpdateETagFile ) {
|
|
||||||
super( "init" );
|
|
||||||
this.frame = frame;
|
|
||||||
this.appConfig = appConfig;
|
|
||||||
this.modsDir = modsDir;
|
|
||||||
this.modsTableStateFile = modsTableStateFile;
|
|
||||||
this.metadataFile = metadataFile;
|
|
||||||
this.catalogFile = catalogFile;
|
|
||||||
this.catalogETagFile = catalogETagFile;
|
|
||||||
this.appUpdateFile = appUpdateFile;
|
|
||||||
this.appUpdateETagFile = appUpdateETagFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
log.error( "Error during ManagerFrame init.", e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void init() throws InterruptedException {
|
|
||||||
|
|
||||||
if ( metadataFile.exists() ) {
|
|
||||||
// Load cached metadata first, before scanning for new info.
|
|
||||||
ModDB cachedDB = JacksonCatalogReader.parse( metadataFile );
|
|
||||||
if ( cachedDB != null ) frame.setLocalModDB( cachedDB );
|
|
||||||
}
|
|
||||||
|
|
||||||
final ListState<ModFileInfo> tableState = loadModsTableState();
|
|
||||||
|
|
||||||
Lock managerLock = frame.getLock();
|
|
||||||
managerLock.lock();
|
|
||||||
try {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() { frame.rescanMods( tableState ); }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Wait until notified that "mods/" has been scanned.
|
|
||||||
while ( frame.isScanning() ) {
|
|
||||||
frame.getScanEndedCondition().await();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
managerLock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
int catalogUpdateInterval = appConfig.getPropertyAsInt( "update_catalog", 0 );
|
|
||||||
boolean needNewCatalog = false;
|
|
||||||
|
|
||||||
// Load the catalog first, before downloading.
|
|
||||||
if ( catalogFile.exists() ) reloadCatalog();
|
|
||||||
|
|
||||||
if ( catalogUpdateInterval > 0 ) {
|
|
||||||
if ( catalogFile.exists() ) {
|
|
||||||
// Check if the downloaded catalog is stale.
|
|
||||||
if ( isFileStale( catalogFile, catalogUpdateInterval ) ) {
|
|
||||||
log.debug( String.format( "Catalog is older than %d days", catalogUpdateInterval ) );
|
|
||||||
needNewCatalog = true;
|
|
||||||
} else {
|
|
||||||
log.debug( "Catalog isn't stale yet" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Catalog file doesn't exist.
|
|
||||||
needNewCatalog = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( needNewCatalog ) {
|
|
||||||
boolean fetched = URLFetcher.refetchURL( ManagerFrame.CATALOG_URL, catalogFile, catalogETagFile );
|
|
||||||
if ( fetched && catalogFile.exists() ) {
|
|
||||||
reloadCatalog();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load the cached info first, before downloading.
|
|
||||||
if ( appUpdateFile.exists() ) reloadAppUpdateInfo();
|
|
||||||
|
|
||||||
int appUpdateInterval = appConfig.getPropertyAsInt( SlipstreamConfig.UPDATE_APP, 0 );
|
|
||||||
boolean needAppUpdate = false;
|
|
||||||
|
|
||||||
if ( appUpdateInterval > 0 ) {
|
|
||||||
if ( appUpdateFile.exists() ) {
|
|
||||||
// Check if the app update info is stale.
|
|
||||||
if ( isFileStale( appUpdateFile, appUpdateInterval ) ) {
|
|
||||||
log.debug( String.format( "App update info is older than %d days", appUpdateInterval ) );
|
|
||||||
needAppUpdate = true;
|
|
||||||
} else {
|
|
||||||
log.debug( "App update info isn't stale yet" );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// App update file doesn't exist.
|
|
||||||
needAppUpdate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( needAppUpdate ) {
|
|
||||||
boolean fetched = URLFetcher.refetchURL( ManagerFrame.APP_UPDATE_URL, appUpdateFile, appUpdateETagFile );
|
|
||||||
if ( fetched && appUpdateFile.exists() ) {
|
|
||||||
reloadAppUpdateInfo();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads modorder.txt and returns a list of mod names in preferred order.
|
|
||||||
*/
|
|
||||||
private ListState<ModFileInfo> loadModsTableState() {
|
|
||||||
List<String> fileNames = new ArrayList<String>();
|
|
||||||
|
|
||||||
BufferedReader br = null;
|
|
||||||
try {
|
|
||||||
FileInputStream is = new FileInputStream( modsTableStateFile );
|
|
||||||
br = new BufferedReader( new InputStreamReader( is, Charset.forName( "UTF-8" ) ) );
|
|
||||||
String line;
|
|
||||||
while ( (line = br.readLine()) != null ) {
|
|
||||||
fileNames.add( line );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( FileNotFoundException e ) {
|
|
||||||
}
|
|
||||||
catch ( IOException e ) {
|
|
||||||
log.error( String.format( "Error reading \"%s\"", modsTableStateFile.getName() ), e );
|
|
||||||
fileNames.clear();
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {if ( br != null ) br.close();}
|
|
||||||
catch ( Exception e ) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListState<ModFileInfo> result = new ListState<ModFileInfo>();
|
|
||||||
|
|
||||||
for ( String fileName : fileNames ) {
|
|
||||||
File modFile = new File( modsDir, fileName );
|
|
||||||
ModFileInfo modFileInfo = new ModFileInfo( modFile );
|
|
||||||
result.addItem( modFileInfo );
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void reloadCatalog() {
|
|
||||||
final ModDB currentDB = JacksonCatalogReader.parse( catalogFile );
|
|
||||||
if ( currentDB != null ) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
frame.setCatalogModDB( currentDB );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void reloadAppUpdateInfo() {
|
|
||||||
final AutoUpdateInfo aui = JacksonAutoUpdateReader.parse( appUpdateFile );
|
|
||||||
if ( aui != null ) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
frame.setAppUpdateInfo( aui );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a file is older than N days.
|
|
||||||
*/
|
|
||||||
private boolean isFileStale( File f, int maxDays ) {
|
|
||||||
Calendar fileCal = Calendar.getInstance();
|
|
||||||
fileCal.setTimeInMillis( f.lastModified() );
|
|
||||||
fileCal.getTimeInMillis(); // Re-calculate calendar fields.
|
|
||||||
|
|
||||||
Calendar freshCal = Calendar.getInstance();
|
|
||||||
freshCal.add( Calendar.DATE, maxDays * -1 );
|
|
||||||
freshCal.getTimeInMillis(); // Re-calculate calendar fields.
|
|
||||||
|
|
||||||
return (fileCal.compareTo( freshCal ) < 0);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,277 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.Color;
|
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.Desktop;
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.Toolkit;
|
|
||||||
import java.awt.datatransfer.StringSelection;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.net.URI;
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.JPopupMenu;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTextPane;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.event.MouseInputAdapter;
|
|
||||||
import javax.swing.text.AttributeSet;
|
|
||||||
import javax.swing.text.BadLocationException;
|
|
||||||
import javax.swing.text.DefaultStyledDocument;
|
|
||||||
import javax.swing.text.SimpleAttributeSet;
|
|
||||||
import javax.swing.text.Style;
|
|
||||||
import javax.swing.text.StyleConstants;
|
|
||||||
import javax.swing.text.StyleContext;
|
|
||||||
import javax.swing.text.StyledDocument;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.Statusbar;
|
|
||||||
|
|
||||||
|
|
||||||
public class ModInfoArea extends JScrollPane {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( ModInfoArea.class );
|
|
||||||
|
|
||||||
private static final String STYLE_REGULAR = "regular";
|
|
||||||
private static final String STYLE_HYPERLINK = "hyperlink";
|
|
||||||
private static final String STYLE_TITLE = "title";
|
|
||||||
private static final String ATTR_HYPERLINK_TARGET = "hyperlink-target";
|
|
||||||
|
|
||||||
public static Color COLOR_HYPER = Color.BLUE;
|
|
||||||
public static final StyleContext DEFAULT_STYLES = ModInfoArea.getDefaultStyleContext();
|
|
||||||
|
|
||||||
private JPopupMenu linkPopup = new JPopupMenu();
|
|
||||||
|
|
||||||
private Statusbar statusbar = null;
|
|
||||||
private String lastClickedLinkTarget = null;
|
|
||||||
|
|
||||||
private JTextPane textPane;
|
|
||||||
private StyledDocument doc;
|
|
||||||
private boolean browseWorks;
|
|
||||||
|
|
||||||
|
|
||||||
public ModInfoArea() {
|
|
||||||
this( DEFAULT_STYLES );
|
|
||||||
}
|
|
||||||
|
|
||||||
public ModInfoArea( StyleContext styleContext ) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
textPane = new JTextPane();
|
|
||||||
textPane.setEditable( false );
|
|
||||||
|
|
||||||
doc = new DefaultStyledDocument( styleContext );
|
|
||||||
textPane.setStyledDocument( doc );
|
|
||||||
|
|
||||||
Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
|
|
||||||
if ( desktop != null && desktop.isSupported( Desktop.Action.BROWSE ) ) {
|
|
||||||
browseWorks = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
linkPopup.add( new AbstractAction( "Copy link address" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent ae ) {
|
|
||||||
if ( lastClickedLinkTarget != null ) {
|
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( new StringSelection( lastClickedLinkTarget ), null );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
MouseInputAdapter hyperlinkListener = new MouseInputAdapter() {
|
|
||||||
private Cursor defaultCursor = new Cursor( Cursor.DEFAULT_CURSOR );
|
|
||||||
private Cursor linkCursor = new Cursor( Cursor.HAND_CURSOR );
|
|
||||||
private boolean wasOverLink = false;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mousePressed( MouseEvent e ) {
|
|
||||||
if ( e.isConsumed() ) return;
|
|
||||||
if ( e.isPopupTrigger() ) showMenu( e );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseReleased( MouseEvent e ) {
|
|
||||||
if ( e.isConsumed() ) return;
|
|
||||||
if ( e.isPopupTrigger() ) showMenu( e );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked( MouseEvent e ) {
|
|
||||||
if ( e.isConsumed() ) return;
|
|
||||||
if ( !SwingUtilities.isLeftMouseButton( e ) ) return;
|
|
||||||
|
|
||||||
AttributeSet tmpAttr = doc.getCharacterElement( textPane.viewToModel( e.getPoint() ) ).getAttributes();
|
|
||||||
Object targetObj = tmpAttr.getAttribute( ATTR_HYPERLINK_TARGET );
|
|
||||||
if ( targetObj != null ) {
|
|
||||||
Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
|
|
||||||
if ( desktop != null && desktop.isSupported( Desktop.Action.BROWSE ) ) {
|
|
||||||
try {
|
|
||||||
desktop.browse( new URI( targetObj.toString() ) );
|
|
||||||
}
|
|
||||||
catch ( Exception f ) {
|
|
||||||
log.error( "Error browsing clicked url: "+ targetObj.toString(), f );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseMoved( MouseEvent e ) {
|
|
||||||
AttributeSet tmpAttr = doc.getCharacterElement( textPane.viewToModel( e.getPoint() ) ).getAttributes();
|
|
||||||
Object targetObj = tmpAttr.getAttribute( ATTR_HYPERLINK_TARGET );
|
|
||||||
if ( targetObj != null ) {
|
|
||||||
textPane.setCursor( linkCursor );
|
|
||||||
if ( statusbar != null )
|
|
||||||
statusbar.setStatusText( targetObj.toString() );
|
|
||||||
wasOverLink = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( wasOverLink ) {
|
|
||||||
textPane.setCursor( defaultCursor );
|
|
||||||
if ( statusbar != null )
|
|
||||||
statusbar.setStatusText( "" );
|
|
||||||
}
|
|
||||||
wasOverLink = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showMenu( MouseEvent e ) {
|
|
||||||
AttributeSet tmpAttr = doc.getCharacterElement( textPane.viewToModel( e.getPoint() ) ).getAttributes();
|
|
||||||
Object targetObj = tmpAttr.getAttribute( ATTR_HYPERLINK_TARGET );
|
|
||||||
if ( targetObj != null ) { // Link menu.
|
|
||||||
textPane.requestFocus();
|
|
||||||
|
|
||||||
lastClickedLinkTarget = targetObj.toString();
|
|
||||||
|
|
||||||
int nx = e.getX();
|
|
||||||
if ( nx > 500 ) nx = nx - linkPopup.getSize().width;
|
|
||||||
|
|
||||||
linkPopup.show( e.getComponent(), nx, e.getY() - linkPopup.getSize().height );
|
|
||||||
|
|
||||||
e.consume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
textPane.addMouseListener( hyperlinkListener );
|
|
||||||
textPane.addMouseMotionListener( hyperlinkListener );
|
|
||||||
|
|
||||||
textPane.addMouseListener( new ClipboardMenuMouseListener() );
|
|
||||||
|
|
||||||
this.setViewportView( textPane );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDescription( String title, String body ) {
|
|
||||||
setDescription( title, null, null, null, body );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDescription( String title, String author, String version, String url, String body ) {
|
|
||||||
Style regularStyle = doc.getStyle( STYLE_REGULAR );
|
|
||||||
try {
|
|
||||||
doc.remove( 0, doc.getLength() );
|
|
||||||
doc.insertString( doc.getLength(), title +"\n", doc.getStyle( STYLE_TITLE ) );
|
|
||||||
|
|
||||||
boolean first = true;
|
|
||||||
if ( author != null ) {
|
|
||||||
doc.insertString( doc.getLength(), String.format( "%sby %s", (first ? "" : " "), author ), regularStyle );
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
if ( version != null ) {
|
|
||||||
doc.insertString( doc.getLength(), String.format( "%s(version %s)", (first ? "" : " "), version ), regularStyle );
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
if ( !first ) {
|
|
||||||
doc.insertString( doc.getLength(), "\n", regularStyle );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( url != null ) {
|
|
||||||
doc.insertString( doc.getLength(), "Website: ", regularStyle );
|
|
||||||
|
|
||||||
if ( browseWorks && url.matches( "^(?:https?|ftp)://.*" ) ) {
|
|
||||||
SimpleAttributeSet tmpAttr = new SimpleAttributeSet( doc.getStyle( STYLE_HYPERLINK ) );
|
|
||||||
tmpAttr.addAttribute( ATTR_HYPERLINK_TARGET, url );
|
|
||||||
doc.insertString( doc.getLength(), "Link", tmpAttr );
|
|
||||||
} else {
|
|
||||||
doc.insertString( doc.getLength(), url, regularStyle );
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.insertString( doc.getLength(), "\n", regularStyle );
|
|
||||||
}
|
|
||||||
|
|
||||||
doc.insertString( doc.getLength(), "\n", regularStyle );
|
|
||||||
|
|
||||||
if ( body != null ) {
|
|
||||||
doc.insertString( doc.getLength(), body, regularStyle );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( BadLocationException e ) {
|
|
||||||
log.error( "Error filling info text area", e );
|
|
||||||
}
|
|
||||||
|
|
||||||
textPane.setCaretPosition( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setCaretPosition( int n ) {
|
|
||||||
textPane.setCaretPosition( n );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
try {
|
|
||||||
doc.remove( 0, doc.getLength() );
|
|
||||||
}
|
|
||||||
catch ( BadLocationException e ) {
|
|
||||||
log.error( "Error clearing info text area", e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendTitleText( String s ) throws BadLocationException {
|
|
||||||
doc.insertString( doc.getLength(), s, doc.getStyle( STYLE_TITLE ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendRegularText( String s ) throws BadLocationException {
|
|
||||||
doc.insertString( doc.getLength(), s, doc.getStyle( STYLE_REGULAR ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void appendLinkText( String linkURL, String linkTitle ) throws BadLocationException {
|
|
||||||
if ( browseWorks && linkURL.matches( "^(?:https?|ftp)://.*" ) ) {
|
|
||||||
SimpleAttributeSet tmpAttr = new SimpleAttributeSet( doc.getStyle( STYLE_HYPERLINK ) );
|
|
||||||
tmpAttr.addAttribute( ATTR_HYPERLINK_TARGET, linkURL );
|
|
||||||
doc.insertString( doc.getLength(), linkTitle, tmpAttr );
|
|
||||||
} else {
|
|
||||||
doc.insertString( doc.getLength(), linkURL, doc.getStyle( STYLE_REGULAR ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a component with a statusbar to be set during mouse events.
|
|
||||||
*/
|
|
||||||
public void setStatusbar( Statusbar comp ) {
|
|
||||||
this.statusbar = comp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private static StyleContext getDefaultStyleContext() {
|
|
||||||
StyleContext result = new StyleContext();
|
|
||||||
Style defaultStyle = StyleContext.getDefaultStyleContext().getStyle( StyleContext.DEFAULT_STYLE );
|
|
||||||
Style baseStyle = result.addStyle( "base", defaultStyle );
|
|
||||||
|
|
||||||
Style regularStyle = result.addStyle( STYLE_REGULAR, baseStyle );
|
|
||||||
StyleConstants.setFontFamily( regularStyle, Font.MONOSPACED );
|
|
||||||
StyleConstants.setFontSize( regularStyle, 12 );
|
|
||||||
|
|
||||||
Style hyperStyle = result.addStyle( STYLE_HYPERLINK, regularStyle );
|
|
||||||
StyleConstants.setForeground( hyperStyle, COLOR_HYPER );
|
|
||||||
StyleConstants.setUnderline( hyperStyle, true );
|
|
||||||
|
|
||||||
Style titleStyle = result.addStyle( STYLE_TITLE, baseStyle );
|
|
||||||
StyleConstants.setFontFamily( titleStyle, Font.SANS_SERIF );
|
|
||||||
StyleConstants.setFontSize( titleStyle, 24 );
|
|
||||||
StyleConstants.setBold( titleStyle, true );
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.Frame;
|
|
||||||
import java.io.File;
|
|
||||||
import javax.swing.JDialog;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.core.ModPatchObserver;
|
|
||||||
import net.vhati.modmanager.ui.ProgressDialog;
|
|
||||||
|
|
||||||
|
|
||||||
public class ModPatchDialog extends ProgressDialog implements ModPatchObserver {
|
|
||||||
|
|
||||||
|
|
||||||
public ModPatchDialog( Frame owner, boolean continueOnSuccess ) {
|
|
||||||
super( owner, continueOnSuccess );
|
|
||||||
this.setTitle( "Patching..." );
|
|
||||||
|
|
||||||
this.setSize( 400, 160 );
|
|
||||||
this.setMinimumSize( this.getPreferredSize() );
|
|
||||||
this.setLocationRelativeTo( owner );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the progress bar.
|
|
||||||
*
|
|
||||||
* If either arg is -1, the bar will become indeterminate.
|
|
||||||
*
|
|
||||||
* @param value the new value
|
|
||||||
* @param max the new maximum
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void patchingProgress( final int value, final int max ) {
|
|
||||||
this.setProgressLater( value, max );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Non-specific activity.
|
|
||||||
*
|
|
||||||
* @param message a string, or null
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void patchingStatus( final String message ) {
|
|
||||||
setStatusTextLater( message != null ? message : "..." );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mod is about to be processed.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void patchingMod( final File modFile ) {
|
|
||||||
setStatusTextLater( String.format( "Installing mod \"%s\"...", modFile.getName() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Patching ended.
|
|
||||||
*
|
|
||||||
* If anything went wrong, e may be non-null.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void patchingEnded( boolean outcome, Exception e ) {
|
|
||||||
setTaskOutcomeLater( outcome, e );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setTaskOutcome( boolean outcome, Exception e ) {
|
|
||||||
super.setTaskOutcome( outcome, e );
|
|
||||||
if ( !this.isShowing() ) return;
|
|
||||||
|
|
||||||
if ( succeeded == true ) {
|
|
||||||
setStatusText( "Patching completed." );
|
|
||||||
} else {
|
|
||||||
setStatusText( String.format( "Patching failed: %s", e ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,591 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Component;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.Font;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.awt.event.FocusAdapter;
|
|
||||||
import java.awt.event.FocusEvent;
|
|
||||||
import java.awt.event.InputEvent;
|
|
||||||
import java.awt.event.KeyEvent;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.StringWriter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.TreeSet;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.Box;
|
|
||||||
import javax.swing.BoxLayout;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JLabel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JOptionPane;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JSplitPane;
|
|
||||||
import javax.swing.JTabbedPane;
|
|
||||||
import javax.swing.JTextArea;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.JTree;
|
|
||||||
import javax.swing.KeyStroke;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.event.AncestorEvent;
|
|
||||||
import javax.swing.event.AncestorListener;
|
|
||||||
import javax.swing.event.CaretEvent;
|
|
||||||
import javax.swing.event.CaretListener;
|
|
||||||
import javax.swing.event.UndoableEditEvent;
|
|
||||||
import javax.swing.event.UndoableEditListener;
|
|
||||||
import javax.swing.text.BadLocationException;
|
|
||||||
import javax.swing.text.Caret;
|
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
import javax.swing.undo.CannotRedoException;
|
|
||||||
import javax.swing.undo.UndoManager;
|
|
||||||
|
|
||||||
import net.vhati.ftldat.AbstractPack;
|
|
||||||
import net.vhati.ftldat.FTLPack;
|
|
||||||
import net.vhati.ftldat.PkgPack;
|
|
||||||
import net.vhati.modmanager.core.ModUtilities;
|
|
||||||
import net.vhati.modmanager.core.SloppyXMLOutputProcessor;
|
|
||||||
import net.vhati.modmanager.core.XMLPatcher;
|
|
||||||
import net.vhati.modmanager.ui.ClipboardMenuMouseListener;
|
|
||||||
|
|
||||||
import org.jdom2.JDOMException;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A basic text editor to test XML modding.
|
|
||||||
*/
|
|
||||||
public class ModXMLSandbox extends JFrame implements ActionListener {
|
|
||||||
|
|
||||||
private static final String baseTitle = "Mod XML Sandbox";
|
|
||||||
|
|
||||||
private UndoManager undoManager = new UndoManager();
|
|
||||||
private String mainText = null;
|
|
||||||
|
|
||||||
private File datsDir;
|
|
||||||
|
|
||||||
private JTabbedPane areasPane;
|
|
||||||
private JScrollPane mainScroll;
|
|
||||||
private JScrollPane appendScroll;
|
|
||||||
private JScrollPane resultScroll;
|
|
||||||
private JSplitPane splitPane;
|
|
||||||
private JScrollPane messageScroll;
|
|
||||||
|
|
||||||
private JTextArea mainArea;
|
|
||||||
private JTextArea appendArea;
|
|
||||||
private JTextArea resultArea;
|
|
||||||
private JTextArea messageArea;
|
|
||||||
private JTextField findField;
|
|
||||||
private JButton openBtn;
|
|
||||||
private JButton patchBtn;
|
|
||||||
private JLabel statusLbl;
|
|
||||||
|
|
||||||
|
|
||||||
public ModXMLSandbox( File datsDir ) {
|
|
||||||
super( baseTitle );
|
|
||||||
this.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );
|
|
||||||
|
|
||||||
this.datsDir = datsDir;
|
|
||||||
|
|
||||||
Font sandboxFont = new Font( Font.MONOSPACED, Font.PLAIN, 13 );
|
|
||||||
|
|
||||||
mainArea = new JTextArea();
|
|
||||||
mainArea.setTabSize( 4 );
|
|
||||||
mainArea.setFont( sandboxFont );
|
|
||||||
mainArea.setEditable( false );
|
|
||||||
mainArea.addMouseListener( new ClipboardMenuMouseListener() );
|
|
||||||
mainScroll = new JScrollPane( mainArea );
|
|
||||||
|
|
||||||
appendArea = new JTextArea();
|
|
||||||
appendArea.setTabSize( 4 );
|
|
||||||
appendArea.setFont( sandboxFont );
|
|
||||||
appendArea.addMouseListener( new ClipboardMenuMouseListener() );
|
|
||||||
appendScroll = new JScrollPane( appendArea );
|
|
||||||
|
|
||||||
resultArea = new JTextArea();
|
|
||||||
resultArea.setTabSize( 4 );
|
|
||||||
resultArea.setFont( sandboxFont );
|
|
||||||
resultArea.setEditable( false );
|
|
||||||
resultArea.addMouseListener( new ClipboardMenuMouseListener() );
|
|
||||||
resultScroll = new JScrollPane( resultArea );
|
|
||||||
|
|
||||||
messageArea = new JTextArea();
|
|
||||||
messageArea.setLineWrap( true );
|
|
||||||
messageArea.setWrapStyleWord( true );
|
|
||||||
messageArea.setTabSize( 4 );
|
|
||||||
messageArea.setFont( sandboxFont );
|
|
||||||
messageArea.setEditable( false );
|
|
||||||
messageArea.addMouseListener( new ClipboardMenuMouseListener() );
|
|
||||||
messageArea.setText( "This is a sandbox to tinker with advanced mod syntax.\n1) Open XML from data.dat to fill the 'main' tab. (ctrl-o)\n2) Write some <mod:command> tags in the 'append' tab. (alt-1,2,3)\n3) Click Patch to see what would happen. (ctrl-p)\nUndo/redo is available. (ctrl-z/ctrl-y)" );
|
|
||||||
messageScroll = new JScrollPane( messageArea );
|
|
||||||
|
|
||||||
JPanel ctrlPanel = new JPanel();
|
|
||||||
ctrlPanel.setLayout( new BoxLayout( ctrlPanel, BoxLayout.X_AXIS ) );
|
|
||||||
|
|
||||||
openBtn = new JButton( "Open Main..." );
|
|
||||||
openBtn.addActionListener( this );
|
|
||||||
ctrlPanel.add( openBtn );
|
|
||||||
|
|
||||||
ctrlPanel.add( Box.createHorizontalGlue() );
|
|
||||||
|
|
||||||
findField = new JTextField( "<find: ctrl-f, F3/shift-F3>", 20 );
|
|
||||||
findField.setMaximumSize( new Dimension( 60, findField.getPreferredSize().height ) );
|
|
||||||
ctrlPanel.add( findField );
|
|
||||||
|
|
||||||
ctrlPanel.add( Box.createHorizontalGlue() );
|
|
||||||
|
|
||||||
patchBtn = new JButton( "Patch" );
|
|
||||||
patchBtn.addActionListener( this );
|
|
||||||
ctrlPanel.add( patchBtn );
|
|
||||||
|
|
||||||
areasPane = new JTabbedPane( JTabbedPane.BOTTOM );
|
|
||||||
areasPane.add( "Main", mainScroll );
|
|
||||||
areasPane.add( "Append", appendScroll );
|
|
||||||
areasPane.add( "Result", resultScroll );
|
|
||||||
|
|
||||||
JPanel topPanel = new JPanel( new BorderLayout() );
|
|
||||||
topPanel.add( areasPane, BorderLayout.CENTER );
|
|
||||||
topPanel.add( ctrlPanel, BorderLayout.SOUTH );
|
|
||||||
|
|
||||||
splitPane = new JSplitPane( JSplitPane.VERTICAL_SPLIT );
|
|
||||||
splitPane.setTopComponent( topPanel );
|
|
||||||
splitPane.setBottomComponent( messageScroll );
|
|
||||||
|
|
||||||
JPanel statusPanel = new JPanel();
|
|
||||||
statusPanel.setLayout( new BoxLayout( statusPanel, BoxLayout.Y_AXIS ) );
|
|
||||||
statusPanel.setBorder( BorderFactory.createLoweredBevelBorder() );
|
|
||||||
statusLbl = new JLabel( " " );
|
|
||||||
statusLbl.setBorder( BorderFactory.createEmptyBorder( 2, 4, 2, 4 ) );
|
|
||||||
statusLbl.setAlignmentX( Component.LEFT_ALIGNMENT );
|
|
||||||
statusPanel.add( statusLbl );
|
|
||||||
|
|
||||||
JPanel contentPane = new JPanel( new BorderLayout() );
|
|
||||||
contentPane.add( splitPane, BorderLayout.CENTER );
|
|
||||||
contentPane.add( statusPanel, BorderLayout.SOUTH );
|
|
||||||
this.setContentPane( contentPane );
|
|
||||||
|
|
||||||
findField.addFocusListener(new FocusAdapter() {
|
|
||||||
@Override
|
|
||||||
public void focusGained( FocusEvent e ) {
|
|
||||||
findField.selectAll();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
CaretListener caretListener = new CaretListener() {
|
|
||||||
@Override
|
|
||||||
public void caretUpdate( CaretEvent e ) {
|
|
||||||
JTextArea currentArea = getCurrentArea();
|
|
||||||
if ( currentArea == null ) return;
|
|
||||||
if ( e.getSource() != currentArea ) return;
|
|
||||||
updateCaretStatus();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
mainArea.addCaretListener( caretListener );
|
|
||||||
appendArea.addCaretListener( caretListener );
|
|
||||||
resultArea.addCaretListener( caretListener );
|
|
||||||
|
|
||||||
CaretAncestorListener caretAncestorListener = new CaretAncestorListener();
|
|
||||||
mainArea.addAncestorListener( caretAncestorListener );
|
|
||||||
appendArea.addAncestorListener( caretAncestorListener );
|
|
||||||
resultArea.addAncestorListener( caretAncestorListener );
|
|
||||||
|
|
||||||
appendArea.getDocument().addUndoableEditListener(new UndoableEditListener() {
|
|
||||||
@Override
|
|
||||||
public void undoableEditHappened( UndoableEditEvent e ) {
|
|
||||||
undoManager.addEdit( e.getEdit() );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
AbstractAction undoAction = new AbstractAction( "Undo" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
try {undoManager.undo();}
|
|
||||||
catch ( CannotRedoException f ) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AbstractAction redoAction = new AbstractAction( "Redo" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
try {undoManager.redo();}
|
|
||||||
catch ( CannotRedoException f ) {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
AbstractAction openAction = new AbstractAction( "Open" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AbstractAction patchAction = new AbstractAction( "Patch" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
patch();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AbstractAction focusFindAction = new AbstractAction( "Focus Find" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
findField.requestFocusInWindow();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AbstractAction findNextAction = new AbstractAction( "Find Next" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
findNext();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AbstractAction findPreviousAction = new AbstractAction( "Find Previous" ) {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
findPrevious();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
KeyStroke undoShortcut = KeyStroke.getKeyStroke( "control Z" );
|
|
||||||
appendArea.getInputMap().put( undoShortcut, "undo" );
|
|
||||||
appendArea.getActionMap().put( "undo", undoAction );
|
|
||||||
KeyStroke redoShortcut = KeyStroke.getKeyStroke( "control Y" );
|
|
||||||
appendArea.getInputMap().put( redoShortcut, "redo" );
|
|
||||||
appendArea.getActionMap().put( "redo", redoAction );
|
|
||||||
|
|
||||||
KeyStroke openShortcut = KeyStroke.getKeyStroke( "control O" );
|
|
||||||
contentPane.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( openShortcut, "open" );
|
|
||||||
contentPane.getActionMap().put( "open", openAction );
|
|
||||||
KeyStroke patchShortcut = KeyStroke.getKeyStroke( "control P" );
|
|
||||||
contentPane.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( patchShortcut, "patch" );
|
|
||||||
contentPane.getActionMap().put( "patch", patchAction );
|
|
||||||
KeyStroke focusFindShortcut = KeyStroke.getKeyStroke( "control F" );
|
|
||||||
contentPane.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( focusFindShortcut, "focus find" );
|
|
||||||
contentPane.getActionMap().put( "focus find", focusFindAction );
|
|
||||||
KeyStroke findNextShortcut = KeyStroke.getKeyStroke( "F3" );
|
|
||||||
contentPane.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( findNextShortcut, "find next" );
|
|
||||||
contentPane.getActionMap().put( "find next", findNextAction );
|
|
||||||
KeyStroke findPreviousShortcut = KeyStroke.getKeyStroke( "shift F3" );
|
|
||||||
contentPane.getInputMap( JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT ).put( findPreviousShortcut, "find previous" );
|
|
||||||
contentPane.getActionMap().put( "find previous", findPreviousAction );
|
|
||||||
|
|
||||||
findField.getInputMap().put( KeyStroke.getKeyStroke( "released ENTER" ), "find next" );
|
|
||||||
findField.getActionMap().put( "find next", findNextAction );
|
|
||||||
|
|
||||||
areasPane.setMnemonicAt( 0, KeyEvent.VK_1 );
|
|
||||||
areasPane.setMnemonicAt( 1, KeyEvent.VK_2 );
|
|
||||||
areasPane.setMnemonicAt( 2, KeyEvent.VK_3 );
|
|
||||||
mainArea.addAncestorListener( new FocusAncestorListener( mainArea ) );
|
|
||||||
appendArea.addAncestorListener( new FocusAncestorListener( appendArea ) );
|
|
||||||
resultArea.addAncestorListener( new FocusAncestorListener( resultArea ) );
|
|
||||||
|
|
||||||
this.pack();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setVisible( boolean b ) {
|
|
||||||
super.setVisible( b );
|
|
||||||
|
|
||||||
if ( b ) {
|
|
||||||
// Splitpane has to be realized before the divider can be moved.
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
splitPane.setDividerLocation( 0.80d );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
Object source = e.getSource();
|
|
||||||
|
|
||||||
if ( source == openBtn ) {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
else if ( source == patchBtn ) {
|
|
||||||
patch();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void open() {
|
|
||||||
messageArea.setText( "" );
|
|
||||||
|
|
||||||
AbstractPack pack = null;
|
|
||||||
InputStream is = null;
|
|
||||||
try {
|
|
||||||
File ftlDatFile = new File( datsDir, "ftl.dat" );
|
|
||||||
File dataDatFile = new File( datsDir, "data.dat" );
|
|
||||||
|
|
||||||
if ( ftlDatFile.exists() ) { // FTL 1.6.1.
|
|
||||||
pack = new PkgPack( ftlDatFile, "r" );
|
|
||||||
}
|
|
||||||
else if ( dataDatFile.exists() ) { // FTL 1.01-1.5.13.
|
|
||||||
pack = new FTLPack( dataDatFile, "r" );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new FileNotFoundException( String.format( "Could not find either \"%s\" or \"%s\"", ftlDatFile.getName(), dataDatFile.getName() ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> innerPaths = pack.list();
|
|
||||||
|
|
||||||
String innerPath = promptForInnerPath( innerPaths );
|
|
||||||
if ( innerPath == null ) return;
|
|
||||||
|
|
||||||
is = pack.getInputStream( innerPath );
|
|
||||||
InputStream rebuiltStream = ModUtilities.rebuildXMLFile( is, "windows-1252", pack.getName()+":"+innerPath );
|
|
||||||
String rebuiltText = ModUtilities.decodeText( rebuiltStream, "Sandbox Main XML" ).text;
|
|
||||||
is.close();
|
|
||||||
|
|
||||||
mainArea.setText( rebuiltText );
|
|
||||||
mainArea.setCaretPosition( 0 );
|
|
||||||
areasPane.setSelectedComponent( mainScroll );
|
|
||||||
resultArea.setText( "" );
|
|
||||||
this.setTitle( String.format( "%s - %s", innerPath, baseTitle ) );
|
|
||||||
}
|
|
||||||
catch ( IOException f ) {
|
|
||||||
messageArea.setText( f.getMessage() );
|
|
||||||
messageArea.setCaretPosition( 0 );
|
|
||||||
}
|
|
||||||
catch ( JDOMException f ) {
|
|
||||||
messageArea.setText( f.getMessage() );
|
|
||||||
messageArea.setCaretPosition( 0 );
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {if ( is != null ) is.close();}
|
|
||||||
catch ( IOException f ) {}
|
|
||||||
|
|
||||||
try {if ( pack != null ) pack.close();}
|
|
||||||
catch ( IOException f ) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void patch() {
|
|
||||||
String mainText = mainArea.getText();
|
|
||||||
if ( mainText.length() == 0 ) return;
|
|
||||||
|
|
||||||
messageArea.setText( "" );
|
|
||||||
|
|
||||||
try {
|
|
||||||
InputStream mainStream = new ByteArrayInputStream( mainText.getBytes( "UTF-8" ) );
|
|
||||||
|
|
||||||
String appendText = appendArea.getText();
|
|
||||||
InputStream appendStream = new ByteArrayInputStream( appendText.getBytes( "UTF-8" ) );
|
|
||||||
|
|
||||||
InputStream resultStream = ModUtilities.patchXMLFile( mainStream, appendStream, "windows-1252", false, "Sandbox Main XML", "Sandbox Append XML" );
|
|
||||||
String resultText = ModUtilities.decodeText( resultStream, "Sandbox Result XML" ).text;
|
|
||||||
|
|
||||||
resultArea.setText( resultText );
|
|
||||||
resultArea.setCaretPosition( 0 );
|
|
||||||
areasPane.setSelectedComponent( resultScroll );
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
messageArea.setText( e.toString() );
|
|
||||||
messageArea.setCaretPosition( 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void findNext() {
|
|
||||||
JTextArea currentArea = getCurrentArea();
|
|
||||||
if ( currentArea == null ) return;
|
|
||||||
|
|
||||||
String query = findField.getText();
|
|
||||||
if ( query.length() == 0 ) return;
|
|
||||||
|
|
||||||
Caret caret = currentArea.getCaret();
|
|
||||||
int from = Math.max( caret.getDot(), caret.getMark() );
|
|
||||||
|
|
||||||
Pattern ptn = Pattern.compile( "(?i)"+ Pattern.quote( query ) );
|
|
||||||
Matcher m = ptn.matcher( currentArea.getText() );
|
|
||||||
if ( m.find(from) ) {
|
|
||||||
caret.setDot( m.start() );
|
|
||||||
caret.moveDot( m.end() );
|
|
||||||
caret.setSelectionVisible( true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void findPrevious() {
|
|
||||||
JTextArea currentArea = getCurrentArea();
|
|
||||||
if ( currentArea == null ) return;
|
|
||||||
|
|
||||||
String query = findField.getText();
|
|
||||||
if ( query.length() == 0 ) return;
|
|
||||||
|
|
||||||
Caret caret = currentArea.getCaret();
|
|
||||||
int from = Math.min( caret.getDot(), caret.getMark() );
|
|
||||||
|
|
||||||
Pattern ptn = Pattern.compile( "(?i)"+ Pattern.quote(query) );
|
|
||||||
Matcher m = ptn.matcher( currentArea.getText() );
|
|
||||||
m.region( 0, from );
|
|
||||||
int lastStart = -1;
|
|
||||||
int lastEnd = -1;
|
|
||||||
while ( m.find() ) {
|
|
||||||
lastStart = m.start();
|
|
||||||
lastEnd = m.end();
|
|
||||||
}
|
|
||||||
if ( lastStart != -1 ) {
|
|
||||||
caret.setDot( lastStart );
|
|
||||||
caret.moveDot( lastEnd );
|
|
||||||
caret.setSelectionVisible( true );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void updateCaretStatus() {
|
|
||||||
JTextArea currentArea = getCurrentArea();
|
|
||||||
if ( currentArea == null ) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
int offset = currentArea.getCaretPosition();
|
|
||||||
int line = currentArea.getLineOfOffset( offset );
|
|
||||||
int lineStart = currentArea.getLineStartOffset( line );
|
|
||||||
int col = offset - lineStart;
|
|
||||||
int lineCount = currentArea.getLineCount();
|
|
||||||
statusLbl.setText( String.format( "Line: %4d/%4d Col: %3d", line+1, lineCount, col+1 ) );
|
|
||||||
}
|
|
||||||
catch ( BadLocationException e ) {
|
|
||||||
statusLbl.setText( String.format( "Line: ???/ ??? Col: ???" ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private JTextArea getCurrentArea() {
|
|
||||||
if ( areasPane.getSelectedIndex() == 0 )
|
|
||||||
return mainArea;
|
|
||||||
else if ( areasPane.getSelectedIndex() == 1 )
|
|
||||||
return appendArea;
|
|
||||||
else if ( areasPane.getSelectedIndex() == 2 )
|
|
||||||
return resultArea;
|
|
||||||
else
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a modal prompt with a JTree representing a list of paths.
|
|
||||||
*
|
|
||||||
* @return the selected path, null otherwise
|
|
||||||
*/
|
|
||||||
private String promptForInnerPath( List<String> innerPaths ) {
|
|
||||||
String result = null;
|
|
||||||
|
|
||||||
Set<String> sortedPaths = new TreeSet<String>( innerPaths );
|
|
||||||
for ( Iterator<String> it = sortedPaths.iterator(); it.hasNext(); ) {
|
|
||||||
if ( !it.next().endsWith(".xml") ) it.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( "/" );
|
|
||||||
DefaultTreeModel treeModel = new DefaultTreeModel( rootNode );
|
|
||||||
|
|
||||||
for ( String innerPath : sortedPaths ) {
|
|
||||||
buildTreeFromString( treeModel, innerPath );
|
|
||||||
}
|
|
||||||
|
|
||||||
JTree pathTree = new JTree( treeModel );
|
|
||||||
pathTree.setRootVisible( false );
|
|
||||||
for ( int i=0; i < pathTree.getRowCount(); i++ ) {
|
|
||||||
pathTree.expandRow( i );
|
|
||||||
}
|
|
||||||
JScrollPane treeScroll = new JScrollPane( pathTree );
|
|
||||||
treeScroll.setPreferredSize( new Dimension( pathTree.getPreferredSize().width, 300 ) );
|
|
||||||
|
|
||||||
pathTree.addAncestorListener( new FocusAncestorListener( pathTree ) );
|
|
||||||
|
|
||||||
int popupResult = JOptionPane.showOptionDialog( this, treeScroll, "Open an XML Resource", JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, new String[]{"OK"}, "OK" );
|
|
||||||
|
|
||||||
if ( popupResult == JOptionPane.OK_OPTION ) {
|
|
||||||
StringBuilder buf = new StringBuilder();
|
|
||||||
|
|
||||||
TreePath selectedPath = pathTree.getSelectionPath();
|
|
||||||
if ( selectedPath != null ) {
|
|
||||||
for ( Object o : selectedPath.getPath() ) {
|
|
||||||
DefaultMutableTreeNode pathComp = (DefaultMutableTreeNode)o;
|
|
||||||
if ( !pathComp.isRoot() ) {
|
|
||||||
Object userObject = pathComp.getUserObject();
|
|
||||||
buf.append( userObject.toString() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( buf.length() > 0 ) result = buf.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds TreeNodes, if they don't already exist, based on a shash-delimited string.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void buildTreeFromString( DefaultTreeModel treeModel, String path ) {
|
|
||||||
// Method commented out to get application to compile. Figure out what this did and fix it
|
|
||||||
// DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
|
|
||||||
// DefaultMutableTreeNode currentNode = rootNode;
|
|
||||||
//
|
|
||||||
// String[] chunks = path.split( "/" );
|
|
||||||
//
|
|
||||||
// for ( int i=0; i < chunks.length; i++ ) {
|
|
||||||
// String chunk = chunks[i];
|
|
||||||
// if ( i < chunks.length-1 )
|
|
||||||
// chunk += "/";
|
|
||||||
//
|
|
||||||
// boolean found = false;
|
|
||||||
// Enumeration<DefaultMutableTreeNode> enumIt = currentNode.children();
|
|
||||||
// while ( enumIt.hasMoreElements() ) {
|
|
||||||
// DefaultMutableTreeNode tmpNode = enumIt.nextElement();
|
|
||||||
// if ( chunk.equals( tmpNode.getUserObject() ) ) {
|
|
||||||
// found = true;
|
|
||||||
// currentNode = tmpNode;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if ( !found ) {
|
|
||||||
// DefaultMutableTreeNode newNode = new DefaultMutableTreeNode( chunk );
|
|
||||||
// currentNode.insert( newNode, currentNode.getChildCount() );
|
|
||||||
// currentNode = newNode;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private class CaretAncestorListener implements AncestorListener {
|
|
||||||
@Override
|
|
||||||
public void ancestorAdded( AncestorEvent e ) {
|
|
||||||
updateCaretStatus();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorMoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorRemoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static class FocusAncestorListener implements AncestorListener {
|
|
||||||
private JComponent comp;
|
|
||||||
|
|
||||||
public FocusAncestorListener( JComponent comp ) {
|
|
||||||
this.comp = comp;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void ancestorAdded( AncestorEvent e ) {
|
|
||||||
comp.requestFocusInWindow();
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorMoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorRemoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* An interface to en/disable user interaction.
|
|
||||||
* It was written with JFrames and glass panes in mind.
|
|
||||||
*/
|
|
||||||
public interface Nerfable {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Either nerf or restore user interaction.
|
|
||||||
*
|
|
||||||
* @param b the nerfed state
|
|
||||||
*/
|
|
||||||
public void setNerfed( boolean b );
|
|
||||||
}
|
|
|
@ -1,224 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Frame;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.Box;
|
|
||||||
import javax.swing.BoxLayout;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JDialog;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JProgressBar;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTextArea;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
|
|
||||||
|
|
||||||
public class ProgressDialog extends JDialog implements ActionListener {
|
|
||||||
|
|
||||||
protected JScrollPane statusScroll;
|
|
||||||
protected JProgressBar progressBar;
|
|
||||||
protected JTextArea statusArea;
|
|
||||||
protected JButton continueBtn;
|
|
||||||
|
|
||||||
protected boolean continueOnSuccess = false;
|
|
||||||
protected boolean done = false;
|
|
||||||
protected boolean succeeded = false;
|
|
||||||
protected Runnable successTask = null;
|
|
||||||
|
|
||||||
|
|
||||||
public ProgressDialog( Frame owner, boolean continueOnSuccess ) {
|
|
||||||
super( owner, true );
|
|
||||||
this.setDefaultCloseOperation( JDialog.DO_NOTHING_ON_CLOSE );
|
|
||||||
|
|
||||||
this.continueOnSuccess = continueOnSuccess;
|
|
||||||
|
|
||||||
progressBar = new JProgressBar();
|
|
||||||
progressBar.setBorderPainted( true );
|
|
||||||
|
|
||||||
JPanel progressHolder = new JPanel( new BorderLayout() );
|
|
||||||
progressHolder.setBorder( BorderFactory.createEmptyBorder( 10, 15, 0, 15 ) );
|
|
||||||
progressHolder.add( progressBar );
|
|
||||||
getContentPane().add( progressHolder, BorderLayout.NORTH );
|
|
||||||
|
|
||||||
statusArea = new JTextArea( 5, 50 );
|
|
||||||
statusArea.setLineWrap( true );
|
|
||||||
statusArea.setWrapStyleWord( true );
|
|
||||||
statusArea.setFont( statusArea.getFont().deriveFont( 13f ) );
|
|
||||||
statusArea.setEditable( false );
|
|
||||||
statusScroll = new JScrollPane( statusArea );
|
|
||||||
|
|
||||||
JPanel statusHolder = new JPanel( new BorderLayout() );
|
|
||||||
statusHolder.setBorder( BorderFactory.createEmptyBorder( 15, 15, 15, 15 ) );
|
|
||||||
statusHolder.add( statusScroll );
|
|
||||||
getContentPane().add( statusHolder, BorderLayout.CENTER );
|
|
||||||
|
|
||||||
continueBtn = new JButton( "Continue" );
|
|
||||||
continueBtn.setEnabled( false );
|
|
||||||
continueBtn.addActionListener( this );
|
|
||||||
|
|
||||||
JPanel continueHolder = new JPanel();
|
|
||||||
continueHolder.setLayout( new BoxLayout( continueHolder, BoxLayout.X_AXIS ) );
|
|
||||||
continueHolder.setBorder( BorderFactory.createEmptyBorder( 0, 0, 10, 0 ) );
|
|
||||||
continueHolder.add( Box.createHorizontalGlue() );
|
|
||||||
continueHolder.add( continueBtn );
|
|
||||||
continueHolder.add( Box.createHorizontalGlue() );
|
|
||||||
getContentPane().add( continueHolder, BorderLayout.SOUTH );
|
|
||||||
|
|
||||||
this.pack();
|
|
||||||
this.setMinimumSize( this.getPreferredSize() );
|
|
||||||
this.setLocationRelativeTo( owner );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
Object source = e.getSource();
|
|
||||||
|
|
||||||
if ( source == continueBtn ) {
|
|
||||||
this.setVisible( false );
|
|
||||||
this.dispose();
|
|
||||||
|
|
||||||
if ( done && succeeded && successTask != null ) {
|
|
||||||
successTask.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the text area's content. (Thread-safe)
|
|
||||||
*
|
|
||||||
* @param message a string, or null
|
|
||||||
*/
|
|
||||||
public void setStatusTextLater( final String message ) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
setStatusText( message != null ? message : "..." );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setStatusText( String message ) {
|
|
||||||
statusArea.setText( message != null ? message : "..." );
|
|
||||||
statusArea.setCaretPosition( 0 );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the progress bar. (Thread-safe)
|
|
||||||
*
|
|
||||||
* If the arg is -1, the bar will become indeterminate.
|
|
||||||
*
|
|
||||||
* @param value the new value
|
|
||||||
*/
|
|
||||||
public void setProgressLater( final int value ) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if ( value >= 0 ) {
|
|
||||||
if ( progressBar.isIndeterminate() )
|
|
||||||
progressBar.setIndeterminate( false );
|
|
||||||
|
|
||||||
progressBar.setValue( value );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( !progressBar.isIndeterminate() )
|
|
||||||
progressBar.setIndeterminate( true );
|
|
||||||
progressBar.setValue( 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the progress bar. (Thread-safe)
|
|
||||||
*
|
|
||||||
* If either arg is -1, the bar will become indeterminate.
|
|
||||||
*
|
|
||||||
* @param value the new value
|
|
||||||
* @param max the new maximum
|
|
||||||
*/
|
|
||||||
public void setProgressLater( final int value, final int max ) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if ( value >= 0 && max >= 0 ) {
|
|
||||||
if ( progressBar.isIndeterminate() )
|
|
||||||
progressBar.setIndeterminate( false );
|
|
||||||
|
|
||||||
if ( progressBar.getMaximum() != max ) {
|
|
||||||
progressBar.setValue( 0 );
|
|
||||||
progressBar.setMaximum( max );
|
|
||||||
}
|
|
||||||
progressBar.setValue( value );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( !progressBar.isIndeterminate() )
|
|
||||||
progressBar.setIndeterminate( true );
|
|
||||||
progressBar.setValue( 0 );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers a response to the immediate task ending. (Thread-safe)
|
|
||||||
*
|
|
||||||
* If anything went wrong, e may be non-null.
|
|
||||||
*/
|
|
||||||
public void setTaskOutcomeLater( final boolean success, final Exception e ) {
|
|
||||||
SwingUtilities.invokeLater(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
setTaskOutcome( success, e );
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void setTaskOutcome( final boolean outcome, final Exception e ) {
|
|
||||||
done = true;
|
|
||||||
succeeded = outcome;
|
|
||||||
|
|
||||||
if ( !ProgressDialog.this.isShowing() ) {
|
|
||||||
// The window's not visible, no continueBtn to click.
|
|
||||||
ProgressDialog.this.dispose();
|
|
||||||
|
|
||||||
if ( succeeded && successTask != null ) {
|
|
||||||
successTask.run();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ( continueOnSuccess && succeeded && successTask != null ) {
|
|
||||||
ProgressDialog.this.setVisible( false );
|
|
||||||
ProgressDialog.this.dispose();
|
|
||||||
successTask.run();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
continueBtn.setEnabled( true );
|
|
||||||
continueBtn.requestFocusInWindow();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets a runnable to trigger after the immediate task ends successfully.
|
|
||||||
*/
|
|
||||||
public void setSuccessTask( Runnable r ) {
|
|
||||||
successTask = r;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows or hides this component depending on the value of parameter b.
|
|
||||||
*
|
|
||||||
* If the immediate task has already completed,
|
|
||||||
* this method will do nothing.
|
|
||||||
*/
|
|
||||||
public void setVisible( boolean b ) {
|
|
||||||
if ( !done ) super.setVisible( b );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,69 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.regex.PatternSyntaxException;
|
|
||||||
import javax.swing.text.AttributeSet;
|
|
||||||
import javax.swing.text.BadLocationException;
|
|
||||||
import javax.swing.text.PlainDocument;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Document thats restricts characters based on a regex.
|
|
||||||
*
|
|
||||||
* @see javax.swing.JTextField.setDocument(javax.swing.text.Ducument)
|
|
||||||
*/
|
|
||||||
public class RegexDocument extends PlainDocument {
|
|
||||||
|
|
||||||
private Pattern pattern = null;
|
|
||||||
|
|
||||||
|
|
||||||
public RegexDocument( Pattern p ) {
|
|
||||||
pattern = p;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegexDocument( String regex ) {
|
|
||||||
try {
|
|
||||||
if ( regex != null && regex.length() > 0 ) {
|
|
||||||
pattern = Pattern.compile( regex );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( PatternSyntaxException e ) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
public RegexDocument() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void insertString( int offs, String str, AttributeSet a ) throws BadLocationException {
|
|
||||||
if ( str == null ) return;
|
|
||||||
|
|
||||||
boolean proceed = true;
|
|
||||||
|
|
||||||
if ( pattern != null ) {
|
|
||||||
String tmp = super.getText( 0, offs ) + str + (super.getLength()>offs ? super.getText( offs, super.getLength()-offs ) : "");
|
|
||||||
Matcher m = pattern.matcher( tmp );
|
|
||||||
proceed = m.matches();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( proceed == true ) super.insertString( offs, str, a );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void remove( int offs, int len ) throws BadLocationException {
|
|
||||||
boolean proceed = true;
|
|
||||||
|
|
||||||
if ( pattern != null ) {
|
|
||||||
try {
|
|
||||||
String tmp = super.getText( 0, offs ) + (super.getLength()>(offs+len) ? super.getText( offs+len, super.getLength()-(offs+len) ) : "");
|
|
||||||
Matcher m = pattern.matcher( tmp );
|
|
||||||
proceed = m.matches();
|
|
||||||
}
|
|
||||||
catch ( BadLocationException e ) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( proceed == true ) super.remove( offs, len );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.Frame;
|
|
||||||
import java.awt.Point;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.ActionListener;
|
|
||||||
import java.io.File;
|
|
||||||
import javax.swing.BorderFactory;
|
|
||||||
import javax.swing.Box;
|
|
||||||
import javax.swing.BoxLayout;
|
|
||||||
import javax.swing.JButton;
|
|
||||||
import javax.swing.JCheckBox;
|
|
||||||
import javax.swing.JDialog;
|
|
||||||
import javax.swing.JFileChooser;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTextField;
|
|
||||||
import javax.swing.ScrollPaneConstants;
|
|
||||||
import javax.swing.event.AncestorEvent;
|
|
||||||
import javax.swing.event.AncestorListener;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.core.FTLUtilities;
|
|
||||||
import net.vhati.modmanager.core.SlipstreamConfig;
|
|
||||||
import net.vhati.modmanager.ui.FieldEditorPanel;
|
|
||||||
import net.vhati.modmanager.ui.FieldEditorPanel.ContentType;
|
|
||||||
|
|
||||||
|
|
||||||
public class SlipstreamConfigDialog extends JDialog implements ActionListener {
|
|
||||||
|
|
||||||
protected static final String ALLOW_ZIP = SlipstreamConfig.ALLOW_ZIP;
|
|
||||||
protected static final String RUN_STEAM_FTL = SlipstreamConfig.RUN_STEAM_FTL;
|
|
||||||
protected static final String NEVER_RUN_FTL = SlipstreamConfig.NEVER_RUN_FTL;
|
|
||||||
protected static final String USE_DEFAULT_UI = SlipstreamConfig.USE_DEFAULT_UI;
|
|
||||||
protected static final String REMEMBER_GEOMETRY = SlipstreamConfig.REMEMBER_GEOMETRY;
|
|
||||||
protected static final String UPDATE_CATALOG = SlipstreamConfig.UPDATE_CATALOG;
|
|
||||||
protected static final String UPDATE_APP = SlipstreamConfig.UPDATE_APP;
|
|
||||||
protected static final String FTL_DATS_PATH = SlipstreamConfig.FTL_DATS_PATH;
|
|
||||||
protected static final String STEAM_EXE_PATH = SlipstreamConfig.STEAM_EXE_PATH;
|
|
||||||
|
|
||||||
protected SlipstreamConfig appConfig;
|
|
||||||
|
|
||||||
protected FieldEditorPanel editorPanel;
|
|
||||||
protected JButton applyBtn;
|
|
||||||
|
|
||||||
|
|
||||||
public SlipstreamConfigDialog( Frame owner, SlipstreamConfig appConfig ) {
|
|
||||||
super( owner, "Preferences..." );
|
|
||||||
this.setDefaultCloseOperation( JDialog.DISPOSE_ON_CLOSE );
|
|
||||||
|
|
||||||
this.appConfig = appConfig;
|
|
||||||
|
|
||||||
editorPanel = new FieldEditorPanel( false );
|
|
||||||
editorPanel.setBorder( BorderFactory.createEmptyBorder( 10, 10, 0, 10 ) );
|
|
||||||
editorPanel.setNameWidth( 250 );
|
|
||||||
|
|
||||||
editorPanel.addRow( ALLOW_ZIP, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addTextRow( "Treat .zip files as .ftl files." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( RUN_STEAM_FTL, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addTextRow( "Use Steam to run FTL, if possible." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( NEVER_RUN_FTL, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addTextRow( "Don't offer to run FTL after patching." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( USE_DEFAULT_UI, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addTextRow( "Don't attempt to resemble a native GUI." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( REMEMBER_GEOMETRY, ContentType.BOOLEAN );
|
|
||||||
editorPanel.addTextRow( "Save window geometry on exit." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( UPDATE_CATALOG, ContentType.INTEGER );
|
|
||||||
editorPanel.addTextRow( "Check for new mod descriptions every N days. (0 to disable)" );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( UPDATE_APP, ContentType.INTEGER );
|
|
||||||
editorPanel.addTextRow( "Check for newer app versions every N days. (0 to disable)" );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( FTL_DATS_PATH, ContentType.CHOOSER );
|
|
||||||
editorPanel.addTextRow( "Path to FTL's resources folder." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addRow( STEAM_EXE_PATH, ContentType.CHOOSER );
|
|
||||||
editorPanel.addTextRow( "Path to Steam's executable." );
|
|
||||||
editorPanel.addSeparatorRow();
|
|
||||||
editorPanel.addBlankRow();
|
|
||||||
editorPanel.addTextRow( "Note: Some changes may have no immediate effect." );
|
|
||||||
editorPanel.addBlankRow();
|
|
||||||
editorPanel.addFillRow();
|
|
||||||
|
|
||||||
editorPanel.getBoolean( ALLOW_ZIP ).setSelected( "true".equals( appConfig.getProperty( SlipstreamConfig.ALLOW_ZIP, "false" ) ) );
|
|
||||||
editorPanel.getBoolean( RUN_STEAM_FTL ).setSelected( "true".equals( appConfig.getProperty( SlipstreamConfig.RUN_STEAM_FTL, "false" ) ) );
|
|
||||||
editorPanel.getBoolean( NEVER_RUN_FTL ).setSelected( "true".equals( appConfig.getProperty( SlipstreamConfig.NEVER_RUN_FTL, "false" ) ) );
|
|
||||||
editorPanel.getBoolean( USE_DEFAULT_UI ).setSelected( "true".equals( appConfig.getProperty( SlipstreamConfig.USE_DEFAULT_UI, "false" ) ) );
|
|
||||||
editorPanel.getBoolean( REMEMBER_GEOMETRY ).setSelected( "true".equals( appConfig.getProperty( SlipstreamConfig.REMEMBER_GEOMETRY, "true" ) ) );
|
|
||||||
editorPanel.getInt( UPDATE_CATALOG ).setText( Integer.toString( appConfig.getPropertyAsInt( SlipstreamConfig.UPDATE_CATALOG, 0 ) ) );
|
|
||||||
editorPanel.getInt( UPDATE_APP ).setText( Integer.toString( appConfig.getPropertyAsInt( SlipstreamConfig.UPDATE_APP, 0 ) ) );
|
|
||||||
|
|
||||||
JTextField ftlDatsPathField = editorPanel.getChooser( FTL_DATS_PATH ).getTextField();
|
|
||||||
ftlDatsPathField.setText( appConfig.getProperty( SlipstreamConfig.FTL_DATS_PATH, "" ) );
|
|
||||||
ftlDatsPathField.setPreferredSize( new Dimension( 150, ftlDatsPathField.getPreferredSize().height ) );
|
|
||||||
editorPanel.getChooser( FTL_DATS_PATH ).getButton().addActionListener( this );
|
|
||||||
|
|
||||||
JTextField steamExePathField = editorPanel.getChooser( STEAM_EXE_PATH ).getTextField();
|
|
||||||
steamExePathField.setText( appConfig.getProperty( SlipstreamConfig.STEAM_EXE_PATH, "" ) );
|
|
||||||
steamExePathField.setPreferredSize( new Dimension( 150, steamExePathField.getPreferredSize().height ) );
|
|
||||||
editorPanel.getChooser( STEAM_EXE_PATH ).getButton().addActionListener( this );
|
|
||||||
|
|
||||||
JPanel ctrlPanel = new JPanel();
|
|
||||||
ctrlPanel.setLayout( new BoxLayout( ctrlPanel, BoxLayout.X_AXIS ) );
|
|
||||||
ctrlPanel.setBorder( BorderFactory.createEmptyBorder( 10, 0, 10, 0 ) );
|
|
||||||
ctrlPanel.add( Box.createHorizontalGlue() );
|
|
||||||
applyBtn = new JButton( "Apply" );
|
|
||||||
applyBtn.addActionListener( this );
|
|
||||||
ctrlPanel.add( applyBtn );
|
|
||||||
ctrlPanel.add( Box.createHorizontalGlue() );
|
|
||||||
|
|
||||||
final JScrollPane editorScroll = new JScrollPane( editorPanel );
|
|
||||||
editorScroll.setVerticalScrollBarPolicy( ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS );
|
|
||||||
editorScroll.getVerticalScrollBar().setUnitIncrement( 10 );
|
|
||||||
int vbarWidth = editorScroll.getVerticalScrollBar().getPreferredSize().width;
|
|
||||||
editorScroll.setPreferredSize( new Dimension( editorPanel.getPreferredSize().width+vbarWidth+5, 400 ) );
|
|
||||||
|
|
||||||
JPanel contentPane = new JPanel( new BorderLayout() );
|
|
||||||
contentPane.add( editorScroll, BorderLayout.CENTER );
|
|
||||||
contentPane.add( ctrlPanel, BorderLayout.SOUTH );
|
|
||||||
this.setContentPane( contentPane );
|
|
||||||
this.pack();
|
|
||||||
this.setMinimumSize( new Dimension( 250, 250 ) );
|
|
||||||
|
|
||||||
|
|
||||||
editorScroll.addAncestorListener(new AncestorListener() {
|
|
||||||
@Override
|
|
||||||
public void ancestorAdded( AncestorEvent e ) {
|
|
||||||
editorScroll.getViewport().setViewPosition( new Point( 0, 0 ) );
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorMoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void ancestorRemoved( AncestorEvent e ) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
Object source = e.getSource();
|
|
||||||
|
|
||||||
if ( source == applyBtn ) {
|
|
||||||
String tmp;
|
|
||||||
appConfig.setProperty( SlipstreamConfig.ALLOW_ZIP, editorPanel.getBoolean( ALLOW_ZIP ).isSelected() ? "true" : "false" );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.RUN_STEAM_FTL, editorPanel.getBoolean( RUN_STEAM_FTL ).isSelected() ? "true" : "false" );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.NEVER_RUN_FTL, editorPanel.getBoolean( NEVER_RUN_FTL ).isSelected() ? "true" : "false" );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.USE_DEFAULT_UI, editorPanel.getBoolean( USE_DEFAULT_UI ).isSelected() ? "true" : "false" );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.REMEMBER_GEOMETRY, editorPanel.getBoolean( REMEMBER_GEOMETRY ).isSelected() ? "true" : "false" );
|
|
||||||
|
|
||||||
tmp = editorPanel.getInt( UPDATE_CATALOG ).getText();
|
|
||||||
try {
|
|
||||||
int n = Integer.parseInt( tmp );
|
|
||||||
n = Math.max( 0, n );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.UPDATE_CATALOG, Integer.toString( n ) );
|
|
||||||
}
|
|
||||||
catch ( NumberFormatException f ) {}
|
|
||||||
|
|
||||||
tmp = editorPanel.getInt( UPDATE_APP ).getText();
|
|
||||||
try {
|
|
||||||
int n = Integer.parseInt( tmp );
|
|
||||||
n = Math.max( 0, n );
|
|
||||||
appConfig.setProperty( SlipstreamConfig.UPDATE_APP, Integer.toString( n ) );
|
|
||||||
}
|
|
||||||
catch ( NumberFormatException f ) {}
|
|
||||||
|
|
||||||
tmp = editorPanel.getChooser( FTL_DATS_PATH ).getTextField().getText();
|
|
||||||
if ( tmp.length() > 0 && FTLUtilities.isDatsDirValid( new File( tmp ) ) ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.FTL_DATS_PATH, tmp );
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = editorPanel.getChooser( STEAM_EXE_PATH ).getTextField().getText();
|
|
||||||
if ( tmp.length() > 0 && new File( tmp ).exists() ) {
|
|
||||||
appConfig.setProperty( SlipstreamConfig.STEAM_EXE_PATH, tmp );
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setVisible( false );
|
|
||||||
this.dispose();
|
|
||||||
}
|
|
||||||
else if ( source == editorPanel.getChooser( FTL_DATS_PATH ).getButton() ) {
|
|
||||||
File datsDir = FTLUtilities.promptForDatsDir( this );
|
|
||||||
if ( datsDir != null ) {
|
|
||||||
editorPanel.getChooser( FTL_DATS_PATH ).getTextField().setText( datsDir.getAbsolutePath() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if ( source == editorPanel.getChooser( STEAM_EXE_PATH ).getButton() ) {
|
|
||||||
String currentPath = editorPanel.getChooser( STEAM_EXE_PATH ).getTextField().getText();
|
|
||||||
|
|
||||||
JFileChooser steamExeChooser = new JFileChooser();
|
|
||||||
steamExeChooser.setDialogTitle( "Find Steam.exe or steam or Steam.app" );
|
|
||||||
steamExeChooser.setFileHidingEnabled( false );
|
|
||||||
steamExeChooser.setMultiSelectionEnabled( false );
|
|
||||||
if ( currentPath.length() > 0 ) {
|
|
||||||
steamExeChooser.setCurrentDirectory( new File( currentPath ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( steamExeChooser.showOpenDialog( null ) == JFileChooser.APPROVE_OPTION ) {
|
|
||||||
File steamExeFile = steamExeChooser.getSelectedFile();
|
|
||||||
if ( steamExeFile.exists() ) {
|
|
||||||
editorPanel.getChooser( STEAM_EXE_PATH ).getTextField().setText( steamExeFile.getAbsolutePath() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
|
|
||||||
public interface Statusbar {
|
|
||||||
public void setStatusText( String text );
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package net.vhati.modmanager.ui;
|
|
||||||
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.awt.event.MouseListener;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.Statusbar;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A MouseListener to show rollover help text in a status bar.
|
|
||||||
*
|
|
||||||
* Construct this with the help text, and a class
|
|
||||||
* implementing the Statusbar interface.
|
|
||||||
*
|
|
||||||
* Then add this mouseListener to a component.
|
|
||||||
*/
|
|
||||||
public class StatusbarMouseListener extends MouseAdapter {
|
|
||||||
|
|
||||||
protected Statusbar bar = null;
|
|
||||||
protected String text = null;
|
|
||||||
|
|
||||||
|
|
||||||
public StatusbarMouseListener( Statusbar bar, String text ) {
|
|
||||||
this.bar = bar;
|
|
||||||
this.text = text;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseEntered( MouseEvent e ) {
|
|
||||||
bar.setStatusText( text );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseExited( MouseEvent e ) {
|
|
||||||
bar.setStatusText( "" );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,117 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.table;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.swing.table.AbstractTableModel;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.core.ModInfo;
|
|
||||||
import net.vhati.modmanager.ui.table.Reorderable;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChecklistTableModel<T> extends AbstractTableModel implements Reorderable {
|
|
||||||
|
|
||||||
private static final int COLUMN_CHECK = 0;
|
|
||||||
private static final int COLUMN_PAYLOAD = 1;
|
|
||||||
|
|
||||||
private static final int DATA_CHECK = 0;
|
|
||||||
private static final int DATA_PAYLOAD = 1;
|
|
||||||
|
|
||||||
private String[] columnNames = new String[] {"?", "Name"};
|
|
||||||
private Class[] columnTypes = new Class[] {Boolean.class, String.class};
|
|
||||||
|
|
||||||
private List<List<Object>> rowsList = new ArrayList<List<Object>>();
|
|
||||||
|
|
||||||
|
|
||||||
public void addItem( T o ) {
|
|
||||||
insertItem( rowsList.size(), false, o );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void insertItem( int row, boolean selected, T o ) {
|
|
||||||
int newRowIndex = rowsList.size();
|
|
||||||
|
|
||||||
List<Object> rowData = new ArrayList<Object>();
|
|
||||||
rowData.add( new Boolean(selected) );
|
|
||||||
rowData.add( o );
|
|
||||||
rowsList.add( row, rowData );
|
|
||||||
|
|
||||||
fireTableRowsInserted( row, row );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeItem( int row ) {
|
|
||||||
rowsList.remove( row );
|
|
||||||
fireTableRowsDeleted( row, row );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeAllItems() {
|
|
||||||
rowsList.clear();
|
|
||||||
fireTableDataChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public T getItem( int row ) {
|
|
||||||
return (T)rowsList.get(row).get( DATA_PAYLOAD );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void reorder( int fromRow, int toRow ) {
|
|
||||||
if ( toRow > fromRow ) toRow--;
|
|
||||||
List<Object> rowData = rowsList.get( fromRow );
|
|
||||||
rowsList.remove( fromRow );
|
|
||||||
fireTableRowsDeleted( fromRow, fromRow );
|
|
||||||
rowsList.add( toRow, rowData );
|
|
||||||
fireTableRowsInserted( toRow, toRow );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSelected( int row, boolean b ) {
|
|
||||||
rowsList.get(row).set( DATA_CHECK, new Boolean( b ) );
|
|
||||||
fireTableRowsUpdated( row, row );
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public boolean isSelected( int row ) {
|
|
||||||
return ((Boolean)rowsList.get( row ).get( DATA_CHECK )).booleanValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getColumnCount() {
|
|
||||||
return columnNames.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getRowCount() {
|
|
||||||
return rowsList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getValueAt( int row, int column ) {
|
|
||||||
if ( column == COLUMN_CHECK ) {
|
|
||||||
return rowsList.get( row ).get( DATA_CHECK );
|
|
||||||
}
|
|
||||||
else if ( column == COLUMN_PAYLOAD ) {
|
|
||||||
Object o = rowsList.get( row ).get( DATA_PAYLOAD );
|
|
||||||
return o.toString();
|
|
||||||
}
|
|
||||||
throw new ArrayIndexOutOfBoundsException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void setValueAt( Object o, int row, int column ) {
|
|
||||||
if ( column == COLUMN_CHECK ) {
|
|
||||||
Boolean bool = (Boolean)o;
|
|
||||||
rowsList.get(row).set( DATA_CHECK, bool );
|
|
||||||
fireTableRowsUpdated( row, row );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isCellEditable( int row, int column ) {
|
|
||||||
if ( column == COLUMN_CHECK ) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Class getColumnClass( int column ) {
|
|
||||||
return columnTypes[column];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.table;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Dimension;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.swing.DropMode;
|
|
||||||
import javax.swing.ListSelectionModel;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTable;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.core.ModFileInfo;
|
|
||||||
import net.vhati.modmanager.ui.table.ChecklistTableModel;
|
|
||||||
import net.vhati.modmanager.ui.table.TableRowTransferHandler;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChecklistTablePanel<T> extends JPanel {
|
|
||||||
|
|
||||||
protected ChecklistTableModel<T>tableModel;
|
|
||||||
protected JTable table;
|
|
||||||
|
|
||||||
|
|
||||||
public ChecklistTablePanel() {
|
|
||||||
super( new BorderLayout() );
|
|
||||||
|
|
||||||
tableModel = new ChecklistTableModel<T>();
|
|
||||||
|
|
||||||
table = new JTable( tableModel );
|
|
||||||
table.setFillsViewportHeight( true );
|
|
||||||
table.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
|
|
||||||
table.setTableHeader( null );
|
|
||||||
table.getColumnModel().getColumn( 0 ).setMinWidth( 30 );
|
|
||||||
table.getColumnModel().getColumn( 0 ).setMaxWidth( 30 );
|
|
||||||
table.getColumnModel().getColumn( 0 ).setPreferredWidth( 30 );
|
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane( null, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
|
|
||||||
scrollPane.setViewportView( table );
|
|
||||||
//scrollPane.setColumnHeaderView( null ); // Counterpart to setTableHeader().
|
|
||||||
scrollPane.setPreferredSize( new Dimension( Integer.MIN_VALUE, Integer.MIN_VALUE ) );
|
|
||||||
this.add( scrollPane, BorderLayout.CENTER );
|
|
||||||
|
|
||||||
|
|
||||||
// Double-click toggles checkboxes.
|
|
||||||
table.addMouseListener(new MouseAdapter() {
|
|
||||||
int prevRow = -1;
|
|
||||||
int streak = 0;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked( MouseEvent e ) {
|
|
||||||
if ( e.getSource() != table ) return;
|
|
||||||
int thisRow = table.rowAtPoint( e.getPoint() );
|
|
||||||
|
|
||||||
// Reset on first click and when no longer on that row.
|
|
||||||
if ( e.getClickCount() == 1 ) prevRow = -1;
|
|
||||||
|
|
||||||
if ( thisRow != prevRow || thisRow == -1 ) {
|
|
||||||
streak = 1;
|
|
||||||
prevRow = thisRow;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
streak++;
|
|
||||||
}
|
|
||||||
if ( streak % 2 != 0 ) return; // Respond only to click pairs.
|
|
||||||
|
|
||||||
// Don't further toggle a multi-clicked checkbox.
|
|
||||||
int viewCol = table.columnAtPoint( e.getPoint() );
|
|
||||||
int modelCol = table.getColumnModel().getColumn( viewCol ).getModelIndex();
|
|
||||||
if ( modelCol == 0 ) return;
|
|
||||||
|
|
||||||
int selRow = table.getSelectedRow();
|
|
||||||
if ( selRow != -1 ) {
|
|
||||||
boolean selected = tableModel.isSelected( selRow );
|
|
||||||
tableModel.setSelected( selRow, !selected );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
table.setTransferHandler( new TableRowTransferHandler( table ) );
|
|
||||||
table.setDropMode( DropMode.INSERT ); // Drop between rows, not on them.
|
|
||||||
table.setDragEnabled( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
tableModel.removeAllItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<T> getAllItems() {
|
|
||||||
List<T> results = new ArrayList<T>();
|
|
||||||
|
|
||||||
for ( int i=0; i < tableModel.getRowCount(); i++ ) {
|
|
||||||
results.add( tableModel.getItem( i ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> getSelectedItems() {
|
|
||||||
List<T> results = new ArrayList<T>();
|
|
||||||
|
|
||||||
for ( int i=0; i < tableModel.getRowCount(); i++ ) {
|
|
||||||
if ( tableModel.isSelected( i ) ) {
|
|
||||||
results.add( tableModel.getItem( i ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void toggleAllItemSelection() {
|
|
||||||
int selectedCount = 0;
|
|
||||||
for ( int i = tableModel.getRowCount()-1; i >= 0; i-- ) {
|
|
||||||
if ( tableModel.isSelected( i ) ) selectedCount++;
|
|
||||||
}
|
|
||||||
boolean b = ( selectedCount != tableModel.getRowCount() );
|
|
||||||
|
|
||||||
for ( int i = tableModel.getRowCount()-1; i >= 0; i-- ) {
|
|
||||||
tableModel.setSelected( i, b );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ChecklistTableModel<T> getTableModel() {
|
|
||||||
return tableModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JTable getTable() {
|
|
||||||
return table;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.table;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation-agnostic model to pass between the GUI thread and the
|
|
||||||
* (de)serializer.
|
|
||||||
*/
|
|
||||||
public class ListState<T> {
|
|
||||||
|
|
||||||
protected List<T> items = new ArrayList<T>();
|
|
||||||
|
|
||||||
|
|
||||||
public ListState() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void addItem( T item ) {
|
|
||||||
items.add( item );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new list containing items in this state.
|
|
||||||
*/
|
|
||||||
public List<T> getItems() {
|
|
||||||
return new ArrayList<T>( items );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeItem( T item ) {
|
|
||||||
items.remove( item );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean containsItem( T item ) {
|
|
||||||
return items.contains( item );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.table;
|
|
||||||
|
|
||||||
|
|
||||||
public interface Reorderable {
|
|
||||||
/**
|
|
||||||
* Moves an element at fromIndex to toIndex.
|
|
||||||
*/
|
|
||||||
public void reorder( int fromIndex, int toIndex );
|
|
||||||
}
|
|
|
@ -1,131 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.table;
|
|
||||||
|
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
|
||||||
import java.awt.datatransfer.Transferable;
|
|
||||||
import java.awt.dnd.DragSource;
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.JTable;
|
|
||||||
import javax.swing.TransferHandler;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.table.Reorderable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows drag and drop reordering of JTable rows.
|
|
||||||
*
|
|
||||||
* Its TableModel must implement the Reorderable interface.
|
|
||||||
*/
|
|
||||||
public class TableRowTransferHandler extends TransferHandler {
|
|
||||||
|
|
||||||
private static final Logger log = LoggerFactory.getLogger( TableRowTransferHandler.class );
|
|
||||||
|
|
||||||
private DataFlavor localIntegerFlavor = null;
|
|
||||||
|
|
||||||
private JTable table = null;
|
|
||||||
|
|
||||||
|
|
||||||
public TableRowTransferHandler( JTable table ) {
|
|
||||||
super();
|
|
||||||
if ( table.getModel() instanceof Reorderable == false ) {
|
|
||||||
throw new IllegalArgumentException( "The tableModel doesn't implement Reorderable." );
|
|
||||||
}
|
|
||||||
this.table = table;
|
|
||||||
|
|
||||||
try {
|
|
||||||
localIntegerFlavor = new DataFlavor( String.format( "%s;class=\"%s\"", DataFlavor.javaJVMLocalObjectMimeType, Integer.class.getName() ) );
|
|
||||||
}
|
|
||||||
catch ( ClassNotFoundException e ) {
|
|
||||||
log.error( "Failed to construct a table row transfer handler", e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Transferable createTransferable( JComponent c ) {
|
|
||||||
assert ( c == table );
|
|
||||||
int row = table.getSelectedRow();
|
|
||||||
return new IntegerTransferrable( new Integer( row ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canImport( TransferHandler.TransferSupport ts ) {
|
|
||||||
boolean b = ( ts.getComponent() == table && ts.isDrop() && ts.isDataFlavorSupported( localIntegerFlavor ) );
|
|
||||||
table.setCursor( b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop );
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSourceActions( JComponent comp ) {
|
|
||||||
return TransferHandler.MOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("Unchecked")
|
|
||||||
public boolean importData( TransferHandler.TransferSupport ts ) {
|
|
||||||
if ( !canImport( ts ) ) return false;
|
|
||||||
|
|
||||||
JTable target = (JTable)ts.getComponent();
|
|
||||||
JTable.DropLocation dl = (JTable.DropLocation)ts.getDropLocation();
|
|
||||||
int dropRow = dl.getRow();
|
|
||||||
int rowCount = table.getModel().getRowCount();
|
|
||||||
if ( dropRow < 0 || dropRow > rowCount ) dropRow = rowCount;
|
|
||||||
|
|
||||||
target.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
|
|
||||||
try {
|
|
||||||
Integer draggedRow = (Integer)ts.getTransferable().getTransferData( localIntegerFlavor );
|
|
||||||
if ( draggedRow != -1 && draggedRow != dropRow ) {
|
|
||||||
((Reorderable)table.getModel()).reorder( draggedRow, dropRow );
|
|
||||||
if ( dropRow > draggedRow ) dropRow--;
|
|
||||||
target.getSelectionModel().addSelectionInterval( dropRow, dropRow );
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
log.error( "Dragging failed", e );
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void exportDone( JComponent source, Transferable data, int action ) {
|
|
||||||
if ( action == TransferHandler.MOVE || action == TransferHandler.NONE ) {
|
|
||||||
table.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drag and drop Integer data, constructed with a raw object
|
|
||||||
* from a drag source, to be transformed into a flavor
|
|
||||||
* suitable for the drop target.
|
|
||||||
*/
|
|
||||||
private class IntegerTransferrable implements Transferable {
|
|
||||||
private Integer data;
|
|
||||||
|
|
||||||
public IntegerTransferrable( Integer data ) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getTransferData( DataFlavor flavor ) {
|
|
||||||
if ( flavor.equals( localIntegerFlavor ) ) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DataFlavor[] getTransferDataFlavors() {
|
|
||||||
return new DataFlavor[] {localIntegerFlavor};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDataFlavorSupported( DataFlavor flavor ) {
|
|
||||||
return flavor.equals( localIntegerFlavor );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,91 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.awt.Component;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JTree;
|
|
||||||
import javax.swing.tree.TreeCellRenderer;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.tree.ChecklistTreePathFilter;
|
|
||||||
import net.vhati.modmanager.ui.tree.ChecklistTreeSelectionModel;
|
|
||||||
import net.vhati.modmanager.ui.tree.TristateCheckBox;
|
|
||||||
import net.vhati.modmanager.ui.tree.TristateButtonModel.TristateState;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cell renderer that augments an existing renderer with a checkbox.
|
|
||||||
*/
|
|
||||||
public class ChecklistTreeCellRenderer extends JPanel implements TreeCellRenderer {
|
|
||||||
|
|
||||||
protected ChecklistTreeSelectionModel selectionModel;
|
|
||||||
protected ChecklistTreePathFilter checklistFilter;
|
|
||||||
protected TreeCellRenderer delegate;
|
|
||||||
protected TristateCheckBox checkbox = new TristateCheckBox();
|
|
||||||
protected int checkMaxX = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* @param delegate a traditional TreeCellRenderer
|
|
||||||
* @param selectionModel a model to query for checkbox states
|
|
||||||
* @param checklistFilter a TreePath filter, or null to always show a checkbox
|
|
||||||
*/
|
|
||||||
public ChecklistTreeCellRenderer( TreeCellRenderer delegate, ChecklistTreeSelectionModel selectionModel, ChecklistTreePathFilter checklistFilter ) {
|
|
||||||
super();
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.selectionModel = selectionModel;
|
|
||||||
this.checklistFilter = checklistFilter;
|
|
||||||
|
|
||||||
this.setLayout( new BorderLayout() );
|
|
||||||
this.setOpaque( false );
|
|
||||||
checkbox.setOpaque( false );
|
|
||||||
|
|
||||||
checkMaxX = checkbox.getPreferredSize().width;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Component getTreeCellRendererComponent( JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus ) {
|
|
||||||
this.removeAll();
|
|
||||||
checkbox.setState( TristateState.DESELECTED );
|
|
||||||
|
|
||||||
Component delegateComp = delegate.getTreeCellRendererComponent( tree, value, sel, expanded, leaf, row, hasFocus );
|
|
||||||
|
|
||||||
TreePath path = tree.getPathForRow( row );
|
|
||||||
if ( path != null ) {
|
|
||||||
if ( selectionModel.isPathSelected( path, selectionModel.isDigged() ) ) {
|
|
||||||
checkbox.setState( TristateState.SELECTED );
|
|
||||||
} else {
|
|
||||||
checkbox.setState( ( selectionModel.isDigged() && selectionModel.isPartiallySelected( path ) ) ? TristateState.INDETERMINATE : TristateState.DESELECTED );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkbox.setVisible( path == null || checklistFilter == null || checklistFilter.isSelectable( path ) );
|
|
||||||
checkbox.setEnabled( tree.isEnabled() );
|
|
||||||
|
|
||||||
this.add( checkbox, BorderLayout.WEST );
|
|
||||||
this.add( delegateComp, BorderLayout.CENTER );
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setDelegate( TreeCellRenderer delegate ) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeCellRenderer getDelegate() {
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the checkbox's right edge (in the renderer component's coordinate space).
|
|
||||||
*
|
|
||||||
* Values less than that can be interpreted as within the checkbox's bounds.
|
|
||||||
* X=0 is the renderer component's left edge.
|
|
||||||
*/
|
|
||||||
public int getCheckboxMaxX() {
|
|
||||||
return checkMaxX;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on CheckTreeManager (rev 120, 2007-07-20)
|
|
||||||
* By Santhosh Kumar T
|
|
||||||
* https://java.net/projects/myswing
|
|
||||||
*
|
|
||||||
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/check/CheckTreeManager.java?rev=120
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MySwing: Advanced Swing Utilites
|
|
||||||
* Copyright (C) 2005 Santhosh Kumar T
|
|
||||||
* <p/>
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
* <p/>
|
|
||||||
* This library 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import javax.swing.JTree;
|
|
||||||
import javax.swing.event.TreeSelectionEvent;
|
|
||||||
import javax.swing.event.TreeSelectionListener;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.tree.ChecklistTreePathFilter;
|
|
||||||
import net.vhati.modmanager.ui.tree.ChecklistTreeSelectionModel;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChecklistTreeManager extends MouseAdapter implements TreeSelectionListener {
|
|
||||||
|
|
||||||
private ChecklistTreeSelectionModel selectionModel;
|
|
||||||
private ChecklistTreePathFilter checklistFilter;
|
|
||||||
protected JTree tree = new JTree();
|
|
||||||
protected int checkMaxX = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor.
|
|
||||||
*
|
|
||||||
* Modifies a given tree to add checkboxes.
|
|
||||||
* - The tree's existing cell renderer will be wrapped with a ChecklistTreeCellRenderer.
|
|
||||||
* - A MouseListener will be added to the tree to detect clicks, which will toggle checkboxes.
|
|
||||||
*
|
|
||||||
* A secondary ChecklistTreeSelectionModel will track checkboxes' states (independent of row
|
|
||||||
* highlighting).
|
|
||||||
*
|
|
||||||
* @param tree a tree to modify
|
|
||||||
* @param dig true show that a node is partially selected by scanning its descendents, false otherwise
|
|
||||||
* @checklistFilter a filter to decide which TreePaths need checkboxes, or null
|
|
||||||
*/
|
|
||||||
public ChecklistTreeManager( JTree tree, boolean dig, ChecklistTreePathFilter checklistFilter ) {
|
|
||||||
this.tree = tree;
|
|
||||||
this.checklistFilter = checklistFilter;
|
|
||||||
|
|
||||||
// Note: If largemodel is not set then treenodes are getting truncated.
|
|
||||||
// Need to debug further to find the problem.
|
|
||||||
if ( checklistFilter != null ) tree.setLargeModel( true );
|
|
||||||
|
|
||||||
selectionModel = new ChecklistTreeSelectionModel( tree.getModel(), dig );
|
|
||||||
|
|
||||||
ChecklistTreeCellRenderer checklistRenderer = new ChecklistTreeCellRenderer( tree.getCellRenderer(), selectionModel, checklistFilter );
|
|
||||||
setCheckboxMaxX( checklistRenderer.getCheckboxMaxX() );
|
|
||||||
tree.setCellRenderer( checklistRenderer );
|
|
||||||
|
|
||||||
selectionModel.addTreeSelectionListener( this );
|
|
||||||
tree.addMouseListener( this );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the checkbox's right edge (in the TreeCellRenderer component's coordinate space).
|
|
||||||
*
|
|
||||||
* Values less than that will be interpreted as within the checkbox's bounds.
|
|
||||||
* X=0 is the renderer component's left edge.
|
|
||||||
*/
|
|
||||||
public void setCheckboxMaxX( int x ) {
|
|
||||||
checkMaxX = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ChecklistTreePathFilter getChecklistFilter() {
|
|
||||||
return checklistFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public ChecklistTreeSelectionModel getSelectionModel() {
|
|
||||||
return selectionModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void mouseClicked( MouseEvent e ) {
|
|
||||||
TreePath path = tree.getPathForLocation( e.getX(), e.getY() );
|
|
||||||
if ( path == null ) return;
|
|
||||||
|
|
||||||
if ( e.getX() > tree.getPathBounds(path).x + checkMaxX ) return;
|
|
||||||
|
|
||||||
if ( checklistFilter != null && !checklistFilter.isSelectable(path) ) return;
|
|
||||||
|
|
||||||
boolean selected = selectionModel.isPathSelected( path, selectionModel.isDigged() );
|
|
||||||
selectionModel.removeTreeSelectionListener( this );
|
|
||||||
|
|
||||||
try {
|
|
||||||
if ( selected ) {
|
|
||||||
selectionModel.removeSelectionPath( path );
|
|
||||||
} else {
|
|
||||||
selectionModel.addSelectionPath( path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
selectionModel.addTreeSelectionListener( this );
|
|
||||||
tree.treeDidChange();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void valueChanged( TreeSelectionEvent e ) {
|
|
||||||
tree.treeDidChange();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,196 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.awt.BorderLayout;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import javax.swing.DropMode;
|
|
||||||
import javax.swing.JFrame;
|
|
||||||
import javax.swing.JPanel;
|
|
||||||
import javax.swing.JScrollPane;
|
|
||||||
import javax.swing.JTree;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
|
||||||
import javax.swing.tree.DefaultTreeCellRenderer;
|
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
import javax.swing.tree.TreeSelectionModel;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.tree.ChecklistTreeManager;
|
|
||||||
import net.vhati.modmanager.ui.tree.ChecklistTreeSelectionModel;
|
|
||||||
import net.vhati.modmanager.ui.tree.TreeTransferHandler;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChecklistTreePanel extends JPanel {
|
|
||||||
|
|
||||||
private DefaultTreeModel treeModel = null;
|
|
||||||
private JTree tree = null;
|
|
||||||
private ChecklistTreeManager checklistManager = null;
|
|
||||||
|
|
||||||
|
|
||||||
public ChecklistTreePanel() {
|
|
||||||
super( new BorderLayout() );
|
|
||||||
|
|
||||||
DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode( "Root", true );
|
|
||||||
treeModel = new DefaultTreeModel( rootNode, true );
|
|
||||||
tree = new JTree( treeModel );
|
|
||||||
tree.setCellRenderer( new DefaultTreeCellRenderer() );
|
|
||||||
tree.setRootVisible( false );
|
|
||||||
tree.getSelectionModel().setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
|
|
||||||
checklistManager = new ChecklistTreeManager( tree, true, null );
|
|
||||||
|
|
||||||
JScrollPane scrollPane = new JScrollPane( tree, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED );
|
|
||||||
this.add( scrollPane, BorderLayout.CENTER );
|
|
||||||
|
|
||||||
tree.setTransferHandler( new TreeTransferHandler( tree ) );
|
|
||||||
tree.setDropMode( DropMode.ON_OR_INSERT ); // Drop between rows, or onto groups.
|
|
||||||
tree.setDragEnabled( true );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all userObjects of nodes with ticked checkboxes (except root itself).
|
|
||||||
*/
|
|
||||||
public List<Object> getSelectedUserObjects() {
|
|
||||||
ChecklistTreeSelectionModel checklistSelectionModel = checklistManager.getSelectionModel();
|
|
||||||
List<Object> results = new ArrayList<Object>();
|
|
||||||
|
|
||||||
for ( Enumeration enumer = checklistSelectionModel.getAllSelectedPaths(); enumer.hasMoreElements(); ) {
|
|
||||||
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)enumer.nextElement();
|
|
||||||
if ( !childNode.isRoot() && childNode.getUserObject() != null ) {
|
|
||||||
results.add( childNode.getUserObject() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all userObjects of all nodes (except root itself).
|
|
||||||
*/
|
|
||||||
public List<Object> getAllUserObjects() {
|
|
||||||
List<Object> results = new ArrayList<Object>();
|
|
||||||
|
|
||||||
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
|
|
||||||
getAllUserObjects( rootNode, results );
|
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void getAllUserObjects( DefaultMutableTreeNode currentNode, List<Object> results ) {
|
|
||||||
if ( !currentNode.isRoot() && currentNode.getUserObject() != null ) {
|
|
||||||
results.add( currentNode.getUserObject() );
|
|
||||||
}
|
|
||||||
|
|
||||||
for ( Enumeration enumer = currentNode.children(); enumer.hasMoreElements(); ) {
|
|
||||||
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)enumer.nextElement();
|
|
||||||
getAllUserObjects( currentNode, results );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
|
|
||||||
rootNode.removeAllChildren();
|
|
||||||
treeModel.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a group to consolidate mods.
|
|
||||||
*
|
|
||||||
* TODO: Trigger a rename.
|
|
||||||
*/
|
|
||||||
public void addGroup() {
|
|
||||||
DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode)treeModel.getRoot();
|
|
||||||
DefaultMutableTreeNode groupNode = new DefaultMutableTreeNode( "New Group", true );
|
|
||||||
rootNode.add( groupNode );
|
|
||||||
treeModel.nodesWereInserted( rootNode, new int[]{rootNode.getIndex( groupNode )} );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disband selected groups.
|
|
||||||
*
|
|
||||||
* TODO
|
|
||||||
*/
|
|
||||||
public void removeSelectedGroups() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rename last selected group.
|
|
||||||
*
|
|
||||||
* TODO
|
|
||||||
*/
|
|
||||||
public void renameSelectedGroup() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cycles through ticking all checkboxes and clearing them.
|
|
||||||
*/
|
|
||||||
public void toggleAllNodeSelection() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cycles through expanding all nodes and collapsing them.
|
|
||||||
*/
|
|
||||||
public void toggleAllNodeExpansion() {
|
|
||||||
boolean canExpand = false;
|
|
||||||
boolean canCollapse = false;
|
|
||||||
|
|
||||||
for ( int i = tree.getRowCount()-1; i >= 0; i-- ) {
|
|
||||||
if ( tree.isCollapsed( i ) ) {
|
|
||||||
canExpand = true;
|
|
||||||
}
|
|
||||||
else if ( tree.isExpanded( i ) ) {
|
|
||||||
canCollapse = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( canExpand ) {
|
|
||||||
expandAllNodes( tree.getRowCount() );
|
|
||||||
}
|
|
||||||
else if ( canCollapse ) {
|
|
||||||
collapseAllNodes( new TreePath( treeModel.getRoot() ) );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expands all nodes by repeatedly expanding until the row count stops
|
|
||||||
* growing.
|
|
||||||
*/
|
|
||||||
public void expandAllNodes( int prevRowCount ) {
|
|
||||||
for ( int i=0; i < prevRowCount; i++ ) {
|
|
||||||
tree.expandRow( i );
|
|
||||||
}
|
|
||||||
if ( tree.getRowCount() != prevRowCount ) {
|
|
||||||
expandAllNodes( tree.getRowCount() );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Collapses all nodes by walking the TreeModel.
|
|
||||||
*/
|
|
||||||
public void collapseAllNodes( TreePath currentPath ) {
|
|
||||||
Object currentNode = currentPath.getLastPathComponent();
|
|
||||||
for ( int i = treeModel.getChildCount( currentNode )-1; i >= 0; i-- ) {
|
|
||||||
Object childNode = treeModel.getChild( currentNode, i );
|
|
||||||
TreePath childPath = currentPath.pathByAddingChild( childNode );
|
|
||||||
collapseAllNodes( childPath );
|
|
||||||
}
|
|
||||||
if ( currentNode != treeModel.getRoot() ) tree.collapsePath( currentPath );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public JTree getTree() {
|
|
||||||
return tree;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DefaultTreeModel getTreeModel() {
|
|
||||||
return treeModel;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChecklistTreeManager getChecklistManager() {
|
|
||||||
return checklistManager;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decides whether a given TreePath should have a checkbox.
|
|
||||||
*/
|
|
||||||
public interface ChecklistTreePathFilter {
|
|
||||||
|
|
||||||
public boolean isSelectable( TreePath path );
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,265 +0,0 @@
|
||||||
/*
|
|
||||||
* Based on CheckTreeSelectionModel (rev 120, 2007-07-20)
|
|
||||||
* By Santhosh Kumar T
|
|
||||||
* https://java.net/projects/myswing
|
|
||||||
*
|
|
||||||
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/check/CheckTreeSelectionModel.java?rev=120
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MySwing: Advanced Swing Utilites
|
|
||||||
* Copyright (C) 2005 Santhosh Kumar T
|
|
||||||
* <p/>
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
* <p/>
|
|
||||||
* This library 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Stack;
|
|
||||||
import javax.swing.tree.DefaultTreeSelectionModel;
|
|
||||||
import javax.swing.tree.TreeModel;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
import javax.swing.tree.TreeSelectionModel;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.tree.PreorderEnumeration;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChecklistTreeSelectionModel extends DefaultTreeSelectionModel {
|
|
||||||
|
|
||||||
private TreeModel model;
|
|
||||||
private boolean dig = true;
|
|
||||||
|
|
||||||
|
|
||||||
public ChecklistTreeSelectionModel( TreeModel model, boolean dig ) {
|
|
||||||
this.model = model;
|
|
||||||
this.dig = dig;
|
|
||||||
this.setSelectionMode( TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDigged() {
|
|
||||||
return dig;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if path1 is a descendant of path2.
|
|
||||||
*/
|
|
||||||
private boolean isDescendant( TreePath path1, TreePath path2 ) {
|
|
||||||
Object obj1[] = path1.getPath();
|
|
||||||
Object obj2[] = path2.getPath();
|
|
||||||
for ( int i=0; i < obj2.length; i++ ) {
|
|
||||||
if ( obj1[i] != obj2[i] ) return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true a selected node exists in the subtree of a given unselected path.
|
|
||||||
* Returns false if the given path is itself selected.
|
|
||||||
*/
|
|
||||||
public boolean isPartiallySelected( TreePath path ) {
|
|
||||||
if ( isPathSelected( path, true ) ) return false;
|
|
||||||
|
|
||||||
TreePath[] selectionPaths = getSelectionPaths();
|
|
||||||
if( selectionPaths == null ) return false;
|
|
||||||
|
|
||||||
for ( int j=0; j < selectionPaths.length; j++ ) {
|
|
||||||
if ( isDescendant( selectionPaths[j], path ) ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if a given path is selected.
|
|
||||||
*
|
|
||||||
* If dig is true, then the path is assumed to be selected, if
|
|
||||||
* one of its ancestors is selected.
|
|
||||||
*/
|
|
||||||
public boolean isPathSelected( TreePath path, boolean dig ) {
|
|
||||||
if ( !dig ) return super.isPathSelected( path );
|
|
||||||
|
|
||||||
while ( path != null && !super.isPathSelected( path ) ) {
|
|
||||||
path = path.getParentPath();
|
|
||||||
}
|
|
||||||
return ( path != null );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSelectionPaths( TreePath[] paths ) {
|
|
||||||
if ( dig ) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
} else {
|
|
||||||
super.setSelectionPaths( paths );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addSelectionPaths( TreePath[] paths ) {
|
|
||||||
if ( !dig ) {
|
|
||||||
super.addSelectionPaths( paths );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unselect all descendants of paths[].
|
|
||||||
for( int i=0; i < paths.length; i++ ) {
|
|
||||||
TreePath path = paths[i];
|
|
||||||
TreePath[] selectionPaths = getSelectionPaths();
|
|
||||||
if ( selectionPaths == null ) break;
|
|
||||||
|
|
||||||
List<TreePath> toBeRemoved = new ArrayList<TreePath>();
|
|
||||||
for ( int j=0; j < selectionPaths.length; j++ ) {
|
|
||||||
if ( isDescendant( selectionPaths[j], path ) ) {
|
|
||||||
toBeRemoved.add( selectionPaths[j] );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.removeSelectionPaths( (TreePath[])toBeRemoved.toArray( new TreePath[0] ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
// If all siblings are selected then unselect them and select parent recursively
|
|
||||||
// otherwize just select that path.
|
|
||||||
for ( int i=0; i < paths.length; i++ ) {
|
|
||||||
TreePath path = paths[i];
|
|
||||||
TreePath temp = null;
|
|
||||||
while ( areSiblingsSelected(path) ) {
|
|
||||||
temp = path;
|
|
||||||
if ( path.getParentPath() == null ) break;
|
|
||||||
path = path.getParentPath();
|
|
||||||
}
|
|
||||||
if ( temp != null ) {
|
|
||||||
if ( temp.getParentPath() != null ) {
|
|
||||||
addSelectionPath( temp.getParentPath() );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if ( !isSelectionEmpty() ) {
|
|
||||||
removeSelectionPaths( getSelectionPaths() );
|
|
||||||
}
|
|
||||||
super.addSelectionPaths( new TreePath[]{temp} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
super.addSelectionPaths( new TreePath[]{path} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void removeSelectionPaths( TreePath[] paths ) {
|
|
||||||
if( !dig ) {
|
|
||||||
super.removeSelectionPaths( paths );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for ( int i=0; i < paths.length; i++ ) {
|
|
||||||
TreePath path = paths[i];
|
|
||||||
if ( path.getPathCount() == 1 ) {
|
|
||||||
super.removeSelectionPaths( new TreePath[]{path} );
|
|
||||||
} else {
|
|
||||||
toggleRemoveSelection( path );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if all siblings of given path are selected.
|
|
||||||
*/
|
|
||||||
private boolean areSiblingsSelected( TreePath path ) {
|
|
||||||
TreePath parent = path.getParentPath();
|
|
||||||
if ( parent == null ) return true;
|
|
||||||
|
|
||||||
Object node = path.getLastPathComponent();
|
|
||||||
Object parentNode = parent.getLastPathComponent();
|
|
||||||
|
|
||||||
int childCount = model.getChildCount( parentNode );
|
|
||||||
for ( int i=0; i < childCount; i++ ) {
|
|
||||||
Object childNode = model.getChild( parentNode, i );
|
|
||||||
if ( childNode == node ) continue;
|
|
||||||
|
|
||||||
if ( !isPathSelected( parent.pathByAddingChild( childNode ) ) ) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unselects a given path, toggling ancestors if they were entirely selected.
|
|
||||||
*
|
|
||||||
* If any ancestor node of the given path is selected, it will be unselected,
|
|
||||||
* and all its descendants - except any within the given path - will be selected.
|
|
||||||
* The ancestor will have gone from fully selected to partially selected.
|
|
||||||
*
|
|
||||||
* Otherwise, the given path will be unselected, and nothing else will change.
|
|
||||||
*/
|
|
||||||
private void toggleRemoveSelection( TreePath path ) {
|
|
||||||
Stack<TreePath> stack = new Stack<TreePath>();
|
|
||||||
TreePath parent = path.getParentPath();
|
|
||||||
|
|
||||||
while ( parent != null && !isPathSelected( parent ) ) {
|
|
||||||
stack.push( parent );
|
|
||||||
parent = parent.getParentPath();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( parent != null ) {
|
|
||||||
stack.push( parent );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
super.removeSelectionPaths( new TreePath[]{path} );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while ( !stack.isEmpty() ) {
|
|
||||||
TreePath temp = stack.pop();
|
|
||||||
TreePath peekPath = ( stack.isEmpty() ? path : stack.peek() );
|
|
||||||
Object node = temp.getLastPathComponent();
|
|
||||||
Object peekNode = peekPath.getLastPathComponent();
|
|
||||||
|
|
||||||
int childCount = model.getChildCount( node );
|
|
||||||
for ( int i=0; i < childCount; i++ ) {
|
|
||||||
Object childNode = model.getChild( node, i );
|
|
||||||
if ( childNode != peekNode ) {
|
|
||||||
super.addSelectionPaths( new TreePath[]{temp.pathByAddingChild( childNode )} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
super.removeSelectionPaths( new TreePath[]{parent} );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Enumeration<TreePath> getAllSelectedPaths() {
|
|
||||||
Enumeration<TreePath> result = null;
|
|
||||||
|
|
||||||
TreePath[] treePaths = getSelectionPaths();
|
|
||||||
if ( treePaths == null ) {
|
|
||||||
List<TreePath> pathsList = Collections.emptyList();
|
|
||||||
result = Collections.enumeration( pathsList );
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
List<TreePath> pathsList = Arrays.asList( treePaths );
|
|
||||||
result = Collections.enumeration( pathsList );
|
|
||||||
if ( dig ) {
|
|
||||||
result = new PreorderEnumeration( result, model );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on ChildrenEnumeration (rev 120, 2007-07-20)
|
|
||||||
* By Santhosh Kumar T
|
|
||||||
* https://java.net/projects/myswing
|
|
||||||
*
|
|
||||||
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/ChildrenEnumeration.java?rev=120
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MySwing: Advanced Swing Utilites
|
|
||||||
* Copyright (C) 2005 Santhosh Kumar T
|
|
||||||
* <p/>
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
* <p/>
|
|
||||||
* This library 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import javax.swing.tree.TreeModel;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
|
|
||||||
|
|
||||||
public class ChildrenEnumeration implements Enumeration<TreePath> {
|
|
||||||
|
|
||||||
private TreePath path;
|
|
||||||
private TreeModel model;
|
|
||||||
private int position = 0;
|
|
||||||
private int childCount;
|
|
||||||
|
|
||||||
|
|
||||||
public ChildrenEnumeration( TreePath path, TreeModel model ) {
|
|
||||||
this.path = path;
|
|
||||||
this.model = model;
|
|
||||||
childCount = model.getChildCount( path.getLastPathComponent() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasMoreElements() {
|
|
||||||
return position < childCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TreePath nextElement() {
|
|
||||||
if( !hasMoreElements() ) throw new NoSuchElementException();
|
|
||||||
|
|
||||||
return path.pathByAddingChild( model.getChild( path.getLastPathComponent(), position++ ) );
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
/**
|
|
||||||
* Based on PreorderEnumeration (rev 120, 2007-07-20)
|
|
||||||
* By Santhosh Kumar T
|
|
||||||
* https://java.net/projects/myswing
|
|
||||||
*
|
|
||||||
* https://java.net/projects/myswing/sources/svn/content/trunk/src/skt/swing/tree/PreorderEnumeration.java?rev=120
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MySwing: Advanced Swing Utilites
|
|
||||||
* Copyright (C) 2005 Santhosh Kumar T
|
|
||||||
* <p/>
|
|
||||||
* This library is free software; you can redistribute it and/or
|
|
||||||
* modify it under the terms of the GNU Lesser General Public
|
|
||||||
* License as published by the Free Software Foundation; either
|
|
||||||
* version 2.1 of the License, or (at your option) any later version.
|
|
||||||
* <p/>
|
|
||||||
* This library 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
|
|
||||||
* Lesser General Public License for more details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Stack;
|
|
||||||
import javax.swing.tree.TreeModel;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
|
|
||||||
|
|
||||||
public class PreorderEnumeration implements Enumeration<TreePath> {
|
|
||||||
|
|
||||||
private TreeModel model;
|
|
||||||
protected Stack<Enumeration<TreePath>> stack = new Stack<Enumeration<TreePath>>();
|
|
||||||
|
|
||||||
|
|
||||||
public PreorderEnumeration( TreePath path, TreeModel model ) {
|
|
||||||
this( Collections.enumeration( Collections.singletonList( path ) ), model );
|
|
||||||
}
|
|
||||||
|
|
||||||
public PreorderEnumeration( Enumeration<TreePath> enumer, TreeModel model ){
|
|
||||||
this.model = model;
|
|
||||||
stack.push( enumer );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasMoreElements() {
|
|
||||||
return ( !stack.empty() && stack.peek().hasMoreElements() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TreePath nextElement() {
|
|
||||||
Enumeration<TreePath> enumer = stack.peek();
|
|
||||||
TreePath path = enumer.nextElement();
|
|
||||||
|
|
||||||
if ( !enumer.hasMoreElements() ) stack.pop();
|
|
||||||
|
|
||||||
if ( model.getChildCount( path.getLastPathComponent() ) > 0 ) {
|
|
||||||
stack.push( new ChildrenEnumeration( path, model ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,190 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation-agnostic model to pass between the GUI thread and the
|
|
||||||
* (de)serializer.
|
|
||||||
*/
|
|
||||||
public class TreeState {
|
|
||||||
|
|
||||||
protected TreeNodeState rootNodeState = null;
|
|
||||||
|
|
||||||
|
|
||||||
public TreeState() {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setRootNodeState( TreeNodeState rootNodeState ) {
|
|
||||||
this.rootNodeState = rootNodeState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNodeState getRootNodeState() {
|
|
||||||
return rootNodeState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public List<TreeNodeState> findNodeStates( TreeNodeStateFilter filter ) {
|
|
||||||
return findNodeStates( getRootNodeState(), filter );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a list of descendant node states which match a given filter.
|
|
||||||
*/
|
|
||||||
public List<TreeNodeState> findNodeStates( TreeNodeState currentNodeState, TreeNodeStateFilter filter ) {
|
|
||||||
List<TreeNodeState> results = new ArrayList<TreeNodeState>( 1 );
|
|
||||||
collectNodeStates( currentNodeState, filter, results );
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean collectNodeStates( TreeNodeState currentNodeState, TreeNodeStateFilter filter, List<TreeNodeState> results ) {
|
|
||||||
int maxResultCount = filter.getMaxResultCount();
|
|
||||||
boolean found = false;
|
|
||||||
|
|
||||||
if ( filter.accept( currentNodeState ) ) {
|
|
||||||
results.add( currentNodeState );
|
|
||||||
if ( maxResultCount > 0 && maxResultCount >= results.size() ) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( currentNodeState.getAllowsChildren() ) {
|
|
||||||
for ( Iterator<TreeNodeState> it = currentNodeState.children(); it.hasNext(); ) {
|
|
||||||
TreeNodeState childNodeState = it.next();
|
|
||||||
found = collectNodeStates( childNodeState, filter, results );
|
|
||||||
if ( found && maxResultCount > 0 && maxResultCount >= results.size() ) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean containsUserObject( Object o ) {
|
|
||||||
UserObjectTreeNodeStateFilter filter = new UserObjectTreeNodeStateFilter( o );
|
|
||||||
filter.setMaxResultCount( 1 );
|
|
||||||
List<TreeNodeState> results = findNodeStates( filter );
|
|
||||||
|
|
||||||
return ( !results.isEmpty() );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static interface TreeNodeStateFilter {
|
|
||||||
public int getMaxResultCount();
|
|
||||||
public boolean accept( TreeNodeState nodeState );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class UserObjectTreeNodeStateFilter implements TreeNodeStateFilter {
|
|
||||||
|
|
||||||
private Class objectClass = null;
|
|
||||||
private Object o = null;
|
|
||||||
private int maxResultCount = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a filter matching objects of a given class (or subclass).
|
|
||||||
*/
|
|
||||||
public UserObjectTreeNodeStateFilter( Class objectClass ) {
|
|
||||||
this.objectClass = objectClass;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a filter matching objects equal to a given object.
|
|
||||||
*/
|
|
||||||
public UserObjectTreeNodeStateFilter( Object o ) {
|
|
||||||
this.o = o;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setMaxResultCount( int n ) { maxResultCount = n; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getMaxResultCount() { return maxResultCount; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean accept( TreeNodeState nodeState ) {
|
|
||||||
Object nodeObject = nodeState.getUserObject();
|
|
||||||
if ( objectClass != null && nodeObject != null ) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
boolean result = objectClass.isAssignableFrom( nodeObject.getClass() );
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else if ( o != null ) {
|
|
||||||
return ( o.equals( nodeState.getUserObject() ) );
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class TreeNodeState {
|
|
||||||
|
|
||||||
protected Object userObject = null;
|
|
||||||
protected boolean expand = false;
|
|
||||||
protected List<TreeNodeState> children = null;
|
|
||||||
private TreeNodeState parentNodeState = null;
|
|
||||||
|
|
||||||
|
|
||||||
public TreeNodeState() {
|
|
||||||
this( false, false );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNodeState( boolean allowsChildren, boolean expand ) {
|
|
||||||
if ( allowsChildren ) {
|
|
||||||
this.expand = expand;
|
|
||||||
children = new ArrayList<TreeNodeState>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets this node's parent to newParent but does not change the
|
|
||||||
* parent's child array.
|
|
||||||
*/
|
|
||||||
public void setParent( TreeNodeState nodeState ) {
|
|
||||||
parentNodeState = nodeState;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TreeNodeState getParent() {
|
|
||||||
return parentNodeState;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public boolean getAllowsChildren() {
|
|
||||||
return ( children != null );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addChild( TreeNodeState childNodeState ) {
|
|
||||||
TreeNodeState oldParent = childNodeState.getParent();
|
|
||||||
if ( oldParent != null ) oldParent.removeChild( childNodeState );
|
|
||||||
|
|
||||||
childNodeState.setParent( this );
|
|
||||||
children.add( childNodeState );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void removeChild( TreeNodeState childNodeState ) {
|
|
||||||
children.remove( childNodeState );
|
|
||||||
childNodeState.setParent( null );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterator over this node state's children.
|
|
||||||
*/
|
|
||||||
public Iterator<TreeNodeState> children() {
|
|
||||||
return children.iterator();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setUserObject( Object userObject ) {
|
|
||||||
this.userObject = userObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Object getUserObject() {
|
|
||||||
return userObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,268 +0,0 @@
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.awt.Cursor;
|
|
||||||
import java.awt.datatransfer.DataFlavor;
|
|
||||||
import java.awt.datatransfer.Transferable;
|
|
||||||
import java.awt.dnd.DragSource;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TreeMap;
|
|
||||||
import javax.swing.JComponent;
|
|
||||||
import javax.swing.JTable;
|
|
||||||
import javax.swing.JTree;
|
|
||||||
import javax.swing.TransferHandler;
|
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
|
||||||
import javax.swing.tree.DefaultTreeModel;
|
|
||||||
import javax.swing.tree.MutableTreeNode;
|
|
||||||
import javax.swing.tree.TreeNode;
|
|
||||||
import javax.swing.tree.TreePath;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A handler to enable drag-and-drop within a JTree.
|
|
||||||
*
|
|
||||||
* When dropped, copies of highlighted nodes will be made via clone() and
|
|
||||||
* inserted at the drop location, then the originals will be removed.
|
|
||||||
*
|
|
||||||
* Dragging onto a space between nodes will insert at that location.
|
|
||||||
* Dragging onto a node that allows children will insert into it.
|
|
||||||
* Dragging onto a node that doesn't allow children will insert after it.
|
|
||||||
*
|
|
||||||
* The TreeModel must be DefaultTreeModel (or a subclass).
|
|
||||||
* All nodes must be DefaultMutableTreeNode (or a subclass) and properly
|
|
||||||
* implement Cloneable.
|
|
||||||
* Set the Jtree's DropMode to ON_OR_INSERT.
|
|
||||||
* The root node must be hidden, to prevent it from being dragged.
|
|
||||||
* The tree's selection model may be set to single or multiple.
|
|
||||||
*/
|
|
||||||
public class TreeTransferHandler extends TransferHandler {
|
|
||||||
|
|
||||||
private DataFlavor localTreePathFlavor = null;
|
|
||||||
private JTree tree = null;
|
|
||||||
|
|
||||||
|
|
||||||
public TreeTransferHandler( JTree tree ) {
|
|
||||||
super();
|
|
||||||
this.tree = tree;
|
|
||||||
|
|
||||||
try {
|
|
||||||
localTreePathFlavor = new DataFlavor( DataFlavor.javaJVMLocalObjectMimeType + ";class=\""+ TreePath[].class.getName() +"\"" );
|
|
||||||
}
|
|
||||||
catch ( ClassNotFoundException e ) {
|
|
||||||
//log.error( e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Transferable createTransferable( JComponent c ) {
|
|
||||||
assert ( c == tree );
|
|
||||||
TreePath[] highlightedPaths = tree.getSelectionPaths();
|
|
||||||
|
|
||||||
Map<Integer,List<TreePath>> pathsByLengthMap = new TreeMap<Integer,List<TreePath>>();
|
|
||||||
for ( TreePath path : highlightedPaths ) {
|
|
||||||
if ( path.getPath().length == 1 ) continue; // Omit root node (shouldn't drag it anyway).
|
|
||||||
|
|
||||||
Integer pathLength = new Integer( path.getPath().length );
|
|
||||||
if ( !pathsByLengthMap.containsKey( pathLength ) ) {
|
|
||||||
pathsByLengthMap.put( pathLength, new ArrayList<TreePath>() );
|
|
||||||
}
|
|
||||||
pathsByLengthMap.get( pathLength ).add( path );
|
|
||||||
}
|
|
||||||
// For each length (shortest-first), iterate its paths.
|
|
||||||
// For each of those paths, search longer lengths' lists,
|
|
||||||
// removing any paths that are descendants of those short ancestor nodes.
|
|
||||||
List<Integer> lengthsList = new ArrayList<Integer>( pathsByLengthMap.keySet() );
|
|
||||||
for ( int i=0; i < lengthsList.size(); i++ ) {
|
|
||||||
for ( TreePath ancestorPath : pathsByLengthMap.get( lengthsList.get( i ) ) ) {
|
|
||||||
for ( int j=i+1; j < lengthsList.size(); j++ ) {
|
|
||||||
|
|
||||||
List<TreePath> childPaths = pathsByLengthMap.get( lengthsList.get( j ) );
|
|
||||||
for ( Iterator<TreePath> childIt = childPaths.iterator(); childIt.hasNext(); ) {
|
|
||||||
TreePath childPath = childIt.next();
|
|
||||||
if ( ancestorPath.isDescendant( childPath ) ) {
|
|
||||||
childIt.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
List<TreePath> uniquePathList = new ArrayList<TreePath>();
|
|
||||||
for ( List<TreePath> paths : pathsByLengthMap.values() ) {
|
|
||||||
uniquePathList.addAll( paths );
|
|
||||||
}
|
|
||||||
TreePath[] uniquePathsArray = uniquePathList.toArray( new TreePath[uniquePathList.size()] );
|
|
||||||
|
|
||||||
return new TreePathTransferrable( uniquePathsArray );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canImport( TransferHandler.TransferSupport ts ) {
|
|
||||||
boolean b = ( ts.getComponent() == tree && ts.isDrop() && ts.isDataFlavorSupported( localTreePathFlavor ) );
|
|
||||||
tree.setCursor( b ? DragSource.DefaultMoveDrop : DragSource.DefaultMoveNoDrop );
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getSourceActions( JComponent comp ) {
|
|
||||||
return TransferHandler.MOVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("Unchecked")
|
|
||||||
public boolean importData( TransferHandler.TransferSupport ts ) {
|
|
||||||
if ( !canImport(ts) ) return false;
|
|
||||||
|
|
||||||
JTree dstTree = (JTree)ts.getComponent();
|
|
||||||
DefaultTreeModel dstTreeModel = (DefaultTreeModel)dstTree.getModel();
|
|
||||||
JTree.DropLocation dl = (JTree.DropLocation)ts.getDropLocation();
|
|
||||||
TreePath dropPath = dl.getPath(); // Dest parent node, or null.
|
|
||||||
int dropIndex = dl.getChildIndex(); // Insertion child index in the dest parent node,
|
|
||||||
// or -1 if dropped onto a group.
|
|
||||||
|
|
||||||
dstTree.setCursor( Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR) );
|
|
||||||
if ( dropPath == null ) return false;
|
|
||||||
MutableTreeNode dropParentNode = (MutableTreeNode)dropPath.getLastPathComponent();
|
|
||||||
|
|
||||||
// When dropping onto a non-group node, insert into the position after it instead.
|
|
||||||
if ( !dropParentNode.getAllowsChildren() ) {
|
|
||||||
MutableTreeNode prevParentNode = dropParentNode;
|
|
||||||
dropPath = dropPath.getParentPath();
|
|
||||||
dropParentNode = (MutableTreeNode)dropPath.getLastPathComponent();
|
|
||||||
dropIndex = dropParentNode.getIndex( prevParentNode ) + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
TreePath[] draggedPaths = (TreePath[])ts.getTransferable().getTransferData( localTreePathFlavor );
|
|
||||||
|
|
||||||
// Bail if the dropPath was among those dragged.
|
|
||||||
boolean badDrop = false;
|
|
||||||
for ( TreePath path : draggedPaths ) {
|
|
||||||
if ( path.equals( dropPath ) ) {
|
|
||||||
badDrop = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( !badDrop && dropParentNode.getAllowsChildren() ) {
|
|
||||||
for ( TreePath path : draggedPaths ) {
|
|
||||||
// Copy the dragged node and any children.
|
|
||||||
DefaultMutableTreeNode srcNode = (DefaultMutableTreeNode)path.getLastPathComponent();
|
|
||||||
MutableTreeNode newNode = (MutableTreeNode)cloneNodes( srcNode );
|
|
||||||
|
|
||||||
if ( dropIndex != -1 ) {
|
|
||||||
// Insert.
|
|
||||||
dropParentNode.insert( newNode, dropIndex );
|
|
||||||
dstTreeModel.nodesWereInserted( dropParentNode, new int[]{dropIndex} );
|
|
||||||
dropIndex++; // Next insertion will be after this node.
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Add to the end.
|
|
||||||
int addIndex = dropParentNode.getChildCount();
|
|
||||||
dropParentNode.insert( newNode, addIndex );
|
|
||||||
dstTreeModel.nodesWereInserted( dropParentNode, new int[]{addIndex} );
|
|
||||||
if ( !dstTree.isExpanded( dropPath ) ) dstTree.expandPath( dropPath );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
// UnsupportedFlavorException: if Transferable.getTransferData() fails.
|
|
||||||
// IOException: if Transferable.getTransferData() fails.
|
|
||||||
// IllegalStateException: if insert/add fails because dropPath's node doesn't allow children.
|
|
||||||
//log.error( e );
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void exportDone( JComponent source, Transferable data, int action ) {
|
|
||||||
if ( action == TransferHandler.MOVE || action == TransferHandler.NONE ) {
|
|
||||||
tree.setCursor( Cursor.getPredefinedCursor( Cursor.DEFAULT_CURSOR ) );
|
|
||||||
}
|
|
||||||
|
|
||||||
JTree srcTree = (JTree)source;
|
|
||||||
DefaultTreeModel srcTreeModel = (DefaultTreeModel)srcTree.getModel();
|
|
||||||
|
|
||||||
if ( action == TransferHandler.MOVE ) {
|
|
||||||
// Remove original dragged rows now that the move completed.
|
|
||||||
|
|
||||||
try {
|
|
||||||
TreePath[] draggedPaths = (TreePath[])data.getTransferData( localTreePathFlavor );
|
|
||||||
for ( TreePath path : draggedPaths ) {
|
|
||||||
MutableTreeNode doomedNode = (MutableTreeNode)path.getLastPathComponent();
|
|
||||||
TreeNode parentNode = doomedNode.getParent();
|
|
||||||
int doomedIndex = parentNode.getIndex( doomedNode );
|
|
||||||
doomedNode.removeFromParent();
|
|
||||||
srcTreeModel.nodesWereRemoved( parentNode, new int[]{doomedIndex}, new Object[]{doomedNode} );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch ( Exception e ) {
|
|
||||||
//log.error( e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively clones a node and its descendants.
|
|
||||||
*
|
|
||||||
* The clone() methods will generally do a shallow copy, sharing
|
|
||||||
* userObjects.
|
|
||||||
*
|
|
||||||
* Sidenote: The parameter couldn't just be MutableTreeNode, because that
|
|
||||||
* doesn't offer the clone() method. And blindly using reflection to
|
|
||||||
* invoke it wouldn't be pretty. Conceivably, a settable factory could be
|
|
||||||
* designed to copy specific custom classes (using constructors instead
|
|
||||||
* of clone(). But that'd be overkill.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("Unchecked")
|
|
||||||
protected MutableTreeNode cloneNodes( DefaultMutableTreeNode srcNode ) {
|
|
||||||
MutableTreeNode resultNode = (MutableTreeNode)srcNode.clone();
|
|
||||||
|
|
||||||
Enumeration enumer = srcNode.children();
|
|
||||||
while ( enumer.hasMoreElements() ) {
|
|
||||||
DefaultMutableTreeNode childNode = (DefaultMutableTreeNode)enumer.nextElement();
|
|
||||||
int addIndex = resultNode.getChildCount();
|
|
||||||
resultNode.insert( cloneNodes( (DefaultMutableTreeNode)childNode ), addIndex );
|
|
||||||
}
|
|
||||||
|
|
||||||
return resultNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Drag and drop TreePath data, constructed with a raw object
|
|
||||||
* from a drag source, to be transformed into a flavor
|
|
||||||
* suitable for the drop target.
|
|
||||||
*/
|
|
||||||
private class TreePathTransferrable implements Transferable {
|
|
||||||
private TreePath[] data;
|
|
||||||
|
|
||||||
public TreePathTransferrable( TreePath[] data ) {
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getTransferData( DataFlavor flavor ) {
|
|
||||||
if ( flavor.equals( localTreePathFlavor ) ) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DataFlavor[] getTransferDataFlavors() {
|
|
||||||
return new DataFlavor[] {localTreePathFlavor};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isDataFlavorSupported( DataFlavor flavor ) {
|
|
||||||
return flavor.equals( localTreePathFlavor );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
/**
|
|
||||||
* Copied from "TristateCheckBox Revisited" (2007-05-25)
|
|
||||||
* By Dr. Heinz M. Kabutz
|
|
||||||
* http://www.javaspecialists.co.za/archive/Issue145.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.awt.event.ItemEvent;
|
|
||||||
import javax.swing.JToggleButton.ToggleButtonModel;
|
|
||||||
|
|
||||||
|
|
||||||
public class TristateButtonModel extends ToggleButtonModel {
|
|
||||||
|
|
||||||
private TristateState state = TristateState.DESELECTED;
|
|
||||||
|
|
||||||
|
|
||||||
public TristateButtonModel( TristateState state ) {
|
|
||||||
setState( state );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TristateButtonModel() {
|
|
||||||
this( TristateState.DESELECTED );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setIndeterminate() {
|
|
||||||
setState( TristateState.INDETERMINATE );
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIndeterminate() {
|
|
||||||
return ( state == TristateState.INDETERMINATE );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setEnabled( boolean enabled ) {
|
|
||||||
super.setEnabled(enabled);
|
|
||||||
// Restore state display.
|
|
||||||
displayState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setSelected( boolean selected ) {
|
|
||||||
setState( selected ? TristateState.SELECTED : TristateState.DESELECTED );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setArmed( boolean b ) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPressed( boolean b ) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void iterateState() {
|
|
||||||
setState( state.next() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setState( TristateState state ) {
|
|
||||||
this.state = state;
|
|
||||||
displayState();
|
|
||||||
if ( state == TristateState.INDETERMINATE && isEnabled() ) {
|
|
||||||
// Send ChangeEvent.
|
|
||||||
fireStateChanged();
|
|
||||||
|
|
||||||
// Send ItemEvent.
|
|
||||||
int indeterminate = 3;
|
|
||||||
fireItemStateChanged(new ItemEvent( this, ItemEvent.ITEM_STATE_CHANGED, this, indeterminate ));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void displayState() {
|
|
||||||
super.setSelected( state != TristateState.DESELECTED );
|
|
||||||
super.setArmed( state == TristateState.INDETERMINATE );
|
|
||||||
super.setPressed( state == TristateState.INDETERMINATE );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TristateState getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static enum TristateState {
|
|
||||||
SELECTED {
|
|
||||||
public TristateState next() {
|
|
||||||
return INDETERMINATE;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
INDETERMINATE {
|
|
||||||
public TristateState next() {
|
|
||||||
return DESELECTED;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DESELECTED {
|
|
||||||
public TristateState next() {
|
|
||||||
return SELECTED;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public abstract TristateState next();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,142 +0,0 @@
|
||||||
/*
|
|
||||||
* Based on "TristateCheckBox Revisited" (2007-05-25)
|
|
||||||
* By Dr. Heinz M. Kabutz
|
|
||||||
* http://www.javaspecialists.co.za/archive/Issue145.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
package net.vhati.modmanager.ui.tree;
|
|
||||||
|
|
||||||
import java.awt.AWTEvent;
|
|
||||||
import java.awt.EventQueue;
|
|
||||||
import java.awt.event.ActionEvent;
|
|
||||||
import java.awt.event.InputEvent;
|
|
||||||
import java.awt.event.MouseAdapter;
|
|
||||||
import java.awt.event.MouseEvent;
|
|
||||||
import java.awt.event.MouseListener;
|
|
||||||
import javax.swing.AbstractAction;
|
|
||||||
import javax.swing.ActionMap;
|
|
||||||
import javax.swing.ButtonModel;
|
|
||||||
import javax.swing.Icon;
|
|
||||||
import javax.swing.JCheckBox;
|
|
||||||
import javax.swing.SwingUtilities;
|
|
||||||
import javax.swing.event.ChangeEvent;
|
|
||||||
import javax.swing.event.ChangeListener;
|
|
||||||
import javax.swing.plaf.ActionMapUIResource;
|
|
||||||
|
|
||||||
import net.vhati.modmanager.ui.tree.TristateButtonModel;
|
|
||||||
import net.vhati.modmanager.ui.tree.TristateButtonModel.TristateState;
|
|
||||||
|
|
||||||
|
|
||||||
public class TristateCheckBox extends JCheckBox {
|
|
||||||
|
|
||||||
private final ChangeListener enableListener;
|
|
||||||
|
|
||||||
|
|
||||||
public TristateCheckBox( String text, Icon icon, TristateState initial ) {
|
|
||||||
super( text, icon );
|
|
||||||
|
|
||||||
setModel( new TristateButtonModel( initial ) );
|
|
||||||
|
|
||||||
enableListener = new ChangeListener() {
|
|
||||||
@Override
|
|
||||||
public void stateChanged( ChangeEvent e ) {
|
|
||||||
TristateCheckBox.this.setFocusable( TristateCheckBox.this.getModel().isEnabled() );
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add a listener for when the mouse is pressed.
|
|
||||||
super.addMouseListener(new MouseAdapter() {
|
|
||||||
@Override
|
|
||||||
public void mousePressed( MouseEvent e ) {
|
|
||||||
TristateCheckBox.this.iterateState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Reset the keyboard action map.
|
|
||||||
ActionMap map = new ActionMapUIResource();
|
|
||||||
map.put( "pressed", new AbstractAction() {
|
|
||||||
@Override
|
|
||||||
public void actionPerformed( ActionEvent e ) {
|
|
||||||
TristateCheckBox.this.iterateState();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
map.put( "released", null );
|
|
||||||
SwingUtilities.replaceUIActionMap( this, map );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TristateCheckBox( String text, TristateState initial ) {
|
|
||||||
this( text, null, initial );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TristateCheckBox( String text ) {
|
|
||||||
this( text, null );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TristateCheckBox() {
|
|
||||||
this( null );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setIndeterminate() {
|
|
||||||
getTristateModel().setIndeterminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isIndeterminate() {
|
|
||||||
return getTristateModel().isIndeterminate();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void setState( TristateState state ) {
|
|
||||||
getTristateModel().setState( state );
|
|
||||||
}
|
|
||||||
|
|
||||||
public TristateState getState() {
|
|
||||||
return getTristateModel().getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setModel( ButtonModel newModel ) {
|
|
||||||
super.setModel( newModel );
|
|
||||||
|
|
||||||
// Listen for enable changes.
|
|
||||||
if ( model instanceof TristateButtonModel ) {
|
|
||||||
model.addChangeListener( enableListener );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public TristateButtonModel getTristateModel() {
|
|
||||||
return (TristateButtonModel)super.getModel();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No one may add mouse listeners, not even Swing!
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void addMouseListener( MouseListener l ) {
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void iterateState() {
|
|
||||||
// Maybe do nothing at all?
|
|
||||||
if ( !super.getModel().isEnabled() ) return;
|
|
||||||
|
|
||||||
this.grabFocus();
|
|
||||||
|
|
||||||
// Iterate state.
|
|
||||||
getTristateModel().iterateState();
|
|
||||||
|
|
||||||
// Fire ActionEvent.
|
|
||||||
int modifiers = 0;
|
|
||||||
AWTEvent currentEvent = EventQueue.getCurrentEvent();
|
|
||||||
if ( currentEvent instanceof InputEvent ) {
|
|
||||||
modifiers = ((InputEvent)currentEvent).getModifiers();
|
|
||||||
}
|
|
||||||
else if ( currentEvent instanceof ActionEvent ) {
|
|
||||||
modifiers = ((ActionEvent)currentEvent).getModifiers();
|
|
||||||
}
|
|
||||||
fireActionPerformed(new ActionEvent( this, ActionEvent.ACTION_PERFORMED, this.getText(), System.currentTimeMillis(), modifiers ));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue