Compare commits
2 commits
ccd50b0275
...
8ffdd5556b
Author | SHA1 | Date | |
---|---|---|---|
8ffdd5556b | |||
0f138f61a8 |
35 changed files with 605 additions and 6592 deletions
2
pom.xml
2
pom.xml
|
@ -67,7 +67,7 @@
|
|||
<dependency>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<version>4.7.7</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
|
|
@ -1,14 +1,8 @@
|
|||
package net.vhati.modmanager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
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.spi.ILoggingEvent;
|
||||
|
@ -20,9 +14,6 @@ import org.slf4j.bridge.SLF4JBridgeHandler;
|
|||
|
||||
import net.vhati.modmanager.cli.SlipstreamCLI;
|
||||
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 {
|
||||
|
@ -31,9 +22,6 @@ public class FTLModManager {
|
|||
|
||||
public static final String APP_NAME = "Slipstream Mod Manager";
|
||||
public static final ComparableVersion APP_VERSION = new ComparableVersion("1.9.1");
|
||||
public static final String APP_URL = "TODO";
|
||||
public static final String APP_AUTHOR = "jan-leila";
|
||||
|
||||
|
||||
public static void main(String[] args) {
|
||||
// Redirect any libraries' java.util.Logging messages.
|
||||
|
@ -52,7 +40,7 @@ public class FTLModManager {
|
|||
encoder.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
|
||||
encoder.start();
|
||||
|
||||
FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
|
||||
FileAppender<ILoggingEvent> fileAppender = new FileAppender<>();
|
||||
fileAppender.setContext(lc);
|
||||
fileAppender.setName("LogFile");
|
||||
fileAppender.setFile(new File("./modman-log.txt").getAbsolutePath());
|
||||
|
@ -68,278 +56,8 @@ public class FTLModManager {
|
|||
log.debug("OS: {} {}", System.getProperty("os.name"), System.getProperty("os.version"));
|
||||
log.debug("VM: {}, {}, {}", System.getProperty("java.vm.name"), System.getProperty("java.version"), System.getProperty("os.arch"));
|
||||
|
||||
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
|
||||
@Override
|
||||
public void uncaughtException( Thread t, Throwable e ) {
|
||||
log.error("Uncaught exception in thread: {}", t.toString(), e);
|
||||
}
|
||||
});
|
||||
Thread.setDefaultUncaughtExceptionHandler((t, e) -> log.error("Uncaught exception in thread: {}", t.toString(), e));
|
||||
|
||||
if ( args.length > 0 ) {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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