first commit
This commit is contained in:
parent
352e1653f8
commit
16a197e856
44 changed files with 5942 additions and 3 deletions
111
src/main/java/net/vhati/modmanager/ui/ChecklistTableModel.java
Normal file
111
src/main/java/net/vhati/modmanager/ui/ChecklistTableModel.java
Normal file
|
@ -0,0 +1,111 @@
|
|||
package net.vhati.modmanager.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
|
||||
import net.vhati.modmanager.core.ModInfo;
|
||||
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
@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];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
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.
|
||||
*/
|
||||
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.isPopupTrigger() ) showMenu( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseReleased( MouseEvent e ) {
|
||||
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 );
|
||||
}
|
||||
}
|
532
src/main/java/net/vhati/modmanager/ui/ManagerFrame.java
Normal file
532
src/main/java/net/vhati/modmanager/ui/ManagerFrame.java
Normal file
|
@ -0,0 +1,532 @@
|
|||
// http://docs.oracle.com/javase/tutorial/uiswing/dnd/emptytable.html
|
||||
// http://stackoverflow.com/questions/638807/how-do-i-drag-and-drop-a-row-in-a-jtable
|
||||
// http://www.java2s.com/Tutorial/Java/0240__Swing/UsingdefaultBooleanvaluecelleditorandrenderer.htm
|
||||
|
||||
package net.vhati.modmanager.ui;
|
||||
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Insets;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.WindowAdapter;
|
||||
import java.awt.event.WindowEvent;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Properties;
|
||||
import javax.swing.BorderFactory;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.DropMode;
|
||||
import javax.swing.JButton;
|
||||
import javax.swing.JFrame;
|
||||
import javax.swing.JLabel;
|
||||
import javax.swing.JOptionPane;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTable;
|
||||
import javax.swing.JTextArea;
|
||||
import javax.swing.ListSelectionModel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
|
||||
import net.vhati.modmanager.core.ComparableVersion;
|
||||
import net.vhati.modmanager.core.FTLUtilities;
|
||||
import net.vhati.modmanager.core.HashObserver;
|
||||
import net.vhati.modmanager.core.HashThread;
|
||||
import net.vhati.modmanager.core.ModDB;
|
||||
import net.vhati.modmanager.core.ModFileInfo;
|
||||
import net.vhati.modmanager.core.ModInfo;
|
||||
import net.vhati.modmanager.core.ModPatchThread;
|
||||
import net.vhati.modmanager.core.ModPatchThread.BackedUpDat;
|
||||
import net.vhati.modmanager.core.ModUtilities;
|
||||
import net.vhati.modmanager.core.Report;
|
||||
import net.vhati.modmanager.json.GrognakCatalogFetcher;
|
||||
import net.vhati.modmanager.json.JacksonGrognakCatalogReader;
|
||||
import net.vhati.modmanager.ui.ChecklistTableModel;
|
||||
import net.vhati.modmanager.ui.ClipboardMenuMouseListener;
|
||||
import net.vhati.modmanager.ui.ModInfoArea;
|
||||
import net.vhati.modmanager.ui.ModPatchDialog;
|
||||
import net.vhati.modmanager.ui.Statusbar;
|
||||
import net.vhati.modmanager.ui.StatusbarMouseListener;
|
||||
import net.vhati.modmanager.ui.TableRowTransferHandler;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
public class ManagerFrame extends JFrame implements ActionListener, HashObserver, Statusbar {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(ManagerFrame.class);
|
||||
|
||||
private File backupDir = new File( "./backup/" );
|
||||
private File modsDir = new File( "./mods/" );
|
||||
|
||||
private int catalogFetchInterval = 7; // Days.
|
||||
private File catalogFile = new File( backupDir, "current_catalog.json" );
|
||||
private File catalogETagFile = new File( backupDir, "current_catalog_etag.txt" );
|
||||
|
||||
private Properties config;
|
||||
private String appName;
|
||||
private ComparableVersion appVersion;
|
||||
|
||||
private HashMap<File,String> modFileHashes = new HashMap<File,String>();
|
||||
private ModDB modDB = new ModDB();
|
||||
|
||||
private ChecklistTableModel<ModFileInfo> localModsTableModel;
|
||||
private JTable localModsTable;
|
||||
|
||||
private JButton patchBtn;
|
||||
private JButton toggleAllBtn;
|
||||
private JButton validateBtn;
|
||||
private JButton aboutBtn;
|
||||
|
||||
private ModInfoArea infoArea;
|
||||
|
||||
private JLabel statusLbl;
|
||||
|
||||
|
||||
public ManagerFrame( Properties config, String appName, ComparableVersion appVersion ) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.appName = appName;
|
||||
this.appVersion = appVersion;
|
||||
|
||||
this.setTitle( String.format( "%s v%s", appName, appVersion ) );
|
||||
|
||||
JPanel contentPane = new JPanel( new BorderLayout() );
|
||||
|
||||
JPanel mainPane = new JPanel( new BorderLayout() );
|
||||
contentPane.add( mainPane, BorderLayout.CENTER );
|
||||
|
||||
JPanel topPanel = new JPanel( new BorderLayout() );
|
||||
|
||||
localModsTableModel = new ChecklistTableModel<ModFileInfo>();
|
||||
|
||||
localModsTable = new JTable( localModsTableModel );
|
||||
localModsTable.setFillsViewportHeight( true );
|
||||
localModsTable.setSelectionMode( ListSelectionModel.SINGLE_SELECTION );
|
||||
localModsTable.setTableHeader( null );
|
||||
localModsTable.getColumnModel().getColumn(0).setMinWidth(30);
|
||||
localModsTable.getColumnModel().getColumn(0).setMaxWidth(30);
|
||||
localModsTable.getColumnModel().getColumn(0).setPreferredWidth(30);
|
||||
|
||||
JScrollPane localModsScroll = new JScrollPane( null, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
|
||||
localModsScroll.setViewportView( localModsTable );
|
||||
//localModsScroll.setColumnHeaderView( null ); // Counterpart to setTableHeader().
|
||||
localModsScroll.setPreferredSize( new Dimension(Integer.MIN_VALUE, Integer.MIN_VALUE) );
|
||||
topPanel.add( localModsScroll, BorderLayout.CENTER );
|
||||
|
||||
JPanel modActionsPanel = new JPanel();
|
||||
modActionsPanel.setLayout( new BoxLayout(modActionsPanel, BoxLayout.Y_AXIS) );
|
||||
modActionsPanel.setBorder( BorderFactory.createEmptyBorder(0,5,5,0) );
|
||||
Insets actionInsets = new Insets(5,10,5,10);
|
||||
|
||||
patchBtn = new JButton("Patch");
|
||||
patchBtn.setMargin( actionInsets );
|
||||
patchBtn.addMouseListener( new StatusbarMouseListener( this, "Incorporate all selected mods into the game." ) );
|
||||
patchBtn.addActionListener(this);
|
||||
modActionsPanel.add( patchBtn );
|
||||
|
||||
toggleAllBtn = new JButton("Toggle All");
|
||||
toggleAllBtn.setMargin( actionInsets );
|
||||
toggleAllBtn.addMouseListener( new StatusbarMouseListener( this, "Select all mods, or none." ) );
|
||||
toggleAllBtn.addActionListener(this);
|
||||
modActionsPanel.add( toggleAllBtn );
|
||||
|
||||
validateBtn = new JButton("Validate");
|
||||
validateBtn.setMargin( actionInsets );
|
||||
validateBtn.addMouseListener( new StatusbarMouseListener( this, "Check selected mods for problems." ) );
|
||||
validateBtn.addActionListener(this);
|
||||
modActionsPanel.add( validateBtn );
|
||||
|
||||
aboutBtn = new JButton("About");
|
||||
aboutBtn.setMargin( actionInsets );
|
||||
aboutBtn.addMouseListener( new StatusbarMouseListener( this, "Show info about this program." ) );
|
||||
aboutBtn.addActionListener(this);
|
||||
modActionsPanel.add( aboutBtn );
|
||||
|
||||
topPanel.add( modActionsPanel, BorderLayout.EAST );
|
||||
|
||||
JButton[] actionBtns = new JButton[] {patchBtn, toggleAllBtn, validateBtn, aboutBtn};
|
||||
int actionBtnWidth = Integer.MIN_VALUE;
|
||||
int actionBtnHeight = Integer.MIN_VALUE;
|
||||
for ( JButton btn : actionBtns ) {
|
||||
actionBtnWidth = Math.max( actionBtnWidth, btn.getPreferredSize().width );
|
||||
actionBtnHeight = Math.max( actionBtnHeight, btn.getPreferredSize().height );
|
||||
}
|
||||
for ( JButton btn : actionBtns ) {
|
||||
Dimension size = new Dimension( actionBtnWidth, actionBtnHeight );
|
||||
btn.setPreferredSize( size );
|
||||
btn.setMinimumSize( size );
|
||||
btn.setMaximumSize( size );
|
||||
}
|
||||
|
||||
mainPane.add( topPanel, BorderLayout.NORTH );
|
||||
|
||||
infoArea = new ModInfoArea();
|
||||
infoArea.setPreferredSize( new Dimension(504, 220) );
|
||||
mainPane.add( infoArea, BorderLayout.CENTER );
|
||||
|
||||
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 );
|
||||
contentPane.add( statusPanel, BorderLayout.SOUTH );
|
||||
|
||||
|
||||
localModsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged( ListSelectionEvent e ) {
|
||||
if ( e.getValueIsAdjusting() ) return;
|
||||
|
||||
int row = localModsTable.getSelectedRow();
|
||||
if ( row == -1 ) return;
|
||||
|
||||
ModFileInfo modFileInfo = localModsTableModel.getItem( row );
|
||||
showLocalModInfo( modFileInfo );
|
||||
}
|
||||
});
|
||||
|
||||
localModsTable.setTransferHandler( new TableRowTransferHandler( localModsTable ) );
|
||||
localModsTable.setDropMode( DropMode.INSERT ); // Drop between rows, not on them.
|
||||
localModsTable.setDragEnabled( true );
|
||||
|
||||
this.setContentPane( contentPane );
|
||||
this.pack();
|
||||
this.setLocationRelativeTo(null);
|
||||
|
||||
showAboutInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extra initialization that must be called after the constructor.
|
||||
* This must be called on the Swing event thread (use invokeLater()).
|
||||
*/
|
||||
public void init() {
|
||||
File[] modFiles = modsDir.listFiles(new FileFilter() {
|
||||
@Override
|
||||
public boolean accept( File f ) {
|
||||
if ( f.isFile() ) {
|
||||
if ( f.getName().endsWith(".ftl") ) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
List<ModFileInfo> unsortedMods = new ArrayList<ModFileInfo>();
|
||||
for ( File f : modFiles ) {
|
||||
ModFileInfo modFileInfo = new ModFileInfo( f );
|
||||
unsortedMods.add( modFileInfo );
|
||||
}
|
||||
|
||||
List<ModFileInfo> sortedMods = loadModOrder( unsortedMods );
|
||||
for ( ModFileInfo modFileInfo : sortedMods ) {
|
||||
localModsTableModel.addItem( modFileInfo );
|
||||
}
|
||||
|
||||
HashThread hashThread = new HashThread( modFiles, this );
|
||||
hashThread.setDaemon( true );
|
||||
hashThread.start();
|
||||
|
||||
boolean needNewCatalog = false;
|
||||
|
||||
if ( catalogFile.exists() ) {
|
||||
// Load the catalog first, before updating.
|
||||
ModDB currentDB = JacksonGrognakCatalogReader.parse( catalogFile );
|
||||
if ( currentDB != null ) modDB = currentDB;
|
||||
|
||||
// Check if the downloaded catalog is stale.
|
||||
Date catalogDate = new Date( catalogFile.lastModified() );
|
||||
Calendar cal = Calendar.getInstance();
|
||||
cal.add( Calendar.DATE, catalogFetchInterval * -1 );
|
||||
if ( catalogDate.before( cal.getTime() ) ) {
|
||||
log.debug( String.format( "Catalog is older than %d days.", catalogFetchInterval ) );
|
||||
needNewCatalog = true;
|
||||
} else {
|
||||
log.debug( "Catalog isn't stale yet." );
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Catalog file doesn't exist.
|
||||
needNewCatalog = true;
|
||||
}
|
||||
|
||||
// Don't update if the user doesn't want to.
|
||||
String updatesAllowed = config.getProperty( "update_catalog", "false" );
|
||||
if ( !updatesAllowed.equals("true") ) needNewCatalog = false;
|
||||
|
||||
if ( needNewCatalog ) {
|
||||
Runnable fetchTask = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String catalogURL = GrognakCatalogFetcher.CATALOG_URL;
|
||||
boolean fetched = GrognakCatalogFetcher.fetchCatalog( catalogURL, catalogFile, catalogETagFile );
|
||||
|
||||
if ( fetched ) reloadCatalog();
|
||||
}
|
||||
};
|
||||
Thread fetchThread = new Thread( fetchTask );
|
||||
fetchThread.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reparses and replace the downloaded ModDB catalog. (thread-safe)
|
||||
*/
|
||||
public void reloadCatalog() {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ( catalogFile.exists() ) {
|
||||
ModDB currentDB = JacksonGrognakCatalogReader.parse( catalogFile );
|
||||
if ( currentDB != null ) modDB = currentDB;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reads modorder.txt and returns a mod list in that order.
|
||||
*
|
||||
* Mods not mentioned in the text appear at the end, alphabetically.
|
||||
* If an error occurs, an alphabetized list is returned.
|
||||
*/
|
||||
private List<ModFileInfo> loadModOrder( List<ModFileInfo> unsortedMods ) {
|
||||
List<ModFileInfo> sortedMods = new ArrayList<ModFileInfo>();
|
||||
List<ModFileInfo> availableMods = new ArrayList<ModFileInfo>( unsortedMods );
|
||||
Collections.sort( availableMods );
|
||||
|
||||
FileInputStream is = null;
|
||||
try {
|
||||
is = new FileInputStream( new File( modsDir, "modorder.txt" ) );
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader( is, Charset.forName("UTF-8") ));
|
||||
String line;
|
||||
while ( (line = br.readLine()) != null ) {
|
||||
Iterator<ModFileInfo> it = availableMods.iterator();
|
||||
while ( it.hasNext() ) {
|
||||
ModFileInfo modFileInfo = it.next();
|
||||
if ( modFileInfo.getName().equals(line) ) {
|
||||
it.remove();
|
||||
sortedMods.add( modFileInfo );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( FileNotFoundException e ) {
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
log.error( "Error reading modorder.txt.", e );
|
||||
}
|
||||
finally {
|
||||
try {if (is != null) is.close();}
|
||||
catch (Exception e) {}
|
||||
}
|
||||
sortedMods.addAll( availableMods );
|
||||
|
||||
return sortedMods;
|
||||
}
|
||||
|
||||
private void saveModOrder( List<ModFileInfo> sortedMods ) {
|
||||
FileOutputStream os = null;
|
||||
try {
|
||||
os = new FileOutputStream( new File( modsDir, "modorder.txt" ) );
|
||||
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter( os, Charset.forName("UTF-8") ));
|
||||
|
||||
for ( ModFileInfo modFileInfo : sortedMods ) {
|
||||
bw.write( modFileInfo.getName() );
|
||||
bw.write( "\r\n" );
|
||||
}
|
||||
bw.flush();
|
||||
}
|
||||
catch ( IOException e ) {
|
||||
log.error( "Error writing modorder.txt.", e );
|
||||
}
|
||||
finally {
|
||||
try {if (os != null) os.close();}
|
||||
catch (Exception e) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void showAboutInfo() {
|
||||
String body = "";
|
||||
body += "- Drag to reorder mods.\n";
|
||||
body += "- Click the checkboxes to select.\n";
|
||||
body += "- Click 'Patch' to apply mods ( select none for vanilla ).\n";
|
||||
body += "\n";
|
||||
body += "Thanks for using this mod manager.\n";
|
||||
body += "Make sure to visit the forum for updates!";
|
||||
|
||||
infoArea.setDescription( appName, "Vhati", appVersion.toString(), "http://abc.net", body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows info about a local mod in the text area.
|
||||
*/
|
||||
public void showLocalModInfo( ModFileInfo modFileInfo ) {
|
||||
String modHash = modFileHashes.get( modFileInfo.getFile() );
|
||||
|
||||
ModInfo modInfo = modDB.getModInfo( modHash );
|
||||
if ( modInfo != null ) {
|
||||
infoArea.setDescription( modInfo.getTitle(), modInfo.getAuthor(), modInfo.getVersion(), modInfo.getURL(), modInfo.getDescription() );
|
||||
}
|
||||
else {
|
||||
String body = "";
|
||||
body += "No info is available for the selected mod.\n\n";
|
||||
body += "If it's stable, please let the Slipstream devs know ";
|
||||
body += "where you found it and include this md5 hash:\n";
|
||||
body += modHash +"\n";
|
||||
infoArea.setDescription( modFileInfo.getName(), body );
|
||||
log.info( String.format("No info for selected mod: %s (%s).", modFileInfo.getName(), modHash) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setStatusText( String text ) {
|
||||
if (text.length() > 0)
|
||||
statusLbl.setText(text);
|
||||
else
|
||||
statusLbl.setText(" ");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e ) {
|
||||
Object source = e.getSource();
|
||||
|
||||
if ( source == patchBtn ) {
|
||||
List<ModFileInfo> sortedMods = new ArrayList<ModFileInfo>();
|
||||
List<File> modFiles = new ArrayList<File>();
|
||||
|
||||
for ( int i=0; i < localModsTableModel.getRowCount(); i++ ) {
|
||||
if ( localModsTableModel.isSelected(i) ) {
|
||||
sortedMods.add( localModsTableModel.getItem(i) );
|
||||
modFiles.add( localModsTableModel.getItem(i).getFile() );
|
||||
}
|
||||
}
|
||||
saveModOrder( sortedMods );
|
||||
|
||||
File datsDir = new File( config.getProperty( "ftl_dats_path" ) );
|
||||
|
||||
BackedUpDat dataDat = new BackedUpDat();
|
||||
dataDat.datFile = new File( datsDir, "data.dat" );
|
||||
dataDat.bakFile = new File( backupDir, "data.dat.bak" );
|
||||
BackedUpDat resDat = new BackedUpDat();
|
||||
resDat.datFile = new File( datsDir, "resource.dat" );
|
||||
resDat.bakFile = new File( backupDir, "resource.dat.bak" );
|
||||
|
||||
ModPatchDialog patchDlg = new ModPatchDialog( this );
|
||||
patchDlg.setSuccessTask( new SpawnGameTask() );
|
||||
|
||||
log.info( "" );
|
||||
log.info( "Patching..." );
|
||||
log.info( "" );
|
||||
ModPatchThread patchThread = new ModPatchThread( modFiles, dataDat, resDat, patchDlg );
|
||||
patchThread.start();
|
||||
|
||||
patchDlg.setVisible( true );
|
||||
}
|
||||
else if ( source == toggleAllBtn ) {
|
||||
int selectedCount = 0;
|
||||
for ( int i = localModsTableModel.getRowCount()-1; i >= 0; i-- ) {
|
||||
if ( localModsTableModel.isSelected(i) ) selectedCount++;
|
||||
}
|
||||
boolean b = ( selectedCount != localModsTableModel.getRowCount() );
|
||||
|
||||
for ( int i = localModsTableModel.getRowCount()-1; i >= 0; i-- ) {
|
||||
localModsTableModel.setSelected( i, b );
|
||||
}
|
||||
}
|
||||
else if ( source == validateBtn ) {
|
||||
StringBuilder resultBuf = new StringBuilder();
|
||||
boolean anyInvalid = false;
|
||||
|
||||
for ( int i = localModsTableModel.getRowCount()-1; i >= 0; i-- ) {
|
||||
if ( !localModsTableModel.isSelected(i) ) continue;
|
||||
|
||||
ModFileInfo modFileInfo = localModsTableModel.getItem( i );
|
||||
Report validateReport = ModUtilities.validateModFile( modFileInfo.getFile(), null );
|
||||
resultBuf.append( validateReport.text );
|
||||
resultBuf.append( "\n" );
|
||||
|
||||
if ( validateReport.outcome == false ) anyInvalid = true;
|
||||
}
|
||||
|
||||
if ( resultBuf.length() == 0 ) {
|
||||
resultBuf.append( "No mods were checked." );
|
||||
}
|
||||
else if ( anyInvalid ) {
|
||||
resultBuf.append( "FTL itself can tolerate lots of errors and still run. " );
|
||||
resultBuf.append( "But invalid XML may break tools that do proper parsing, " );
|
||||
resultBuf.append( "and it hinders the development of new tools.\n" );
|
||||
}
|
||||
infoArea.setDescription( "Results", resultBuf.toString() );
|
||||
}
|
||||
else if ( source == aboutBtn ) {
|
||||
showAboutInfo();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void hashCalculated( final File f, final String hash ) {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
modFileHashes.put( f, hash );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private class SpawnGameTask implements Runnable {
|
||||
@Override
|
||||
public void run() {
|
||||
String neverRunFtl = config.getProperty( "never_run_ftl", "false" );
|
||||
if ( !neverRunFtl.equals("true") ) {
|
||||
File datsDir = new File( config.getProperty( "ftl_dats_path" ) );
|
||||
|
||||
File exeFile = FTLUtilities.findGameExe( datsDir );
|
||||
if ( exeFile != null ) {
|
||||
int response = JOptionPane.showConfirmDialog( ManagerFrame.this, "Do you want to run the game now?", "Ready to Play", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE );
|
||||
if ( response == JOptionPane.YES_OPTION ) {
|
||||
log.info( "Launching FTL..." );
|
||||
try {
|
||||
FTLUtilities.launchGame( exeFile );
|
||||
} catch ( Exception e ) {
|
||||
log.error( "Error launching FTL.", e );
|
||||
}
|
||||
System.exit( 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
152
src/main/java/net/vhati/modmanager/ui/ModInfoArea.java
Normal file
152
src/main/java/net/vhati/modmanager/ui/ModInfoArea.java
Normal file
|
@ -0,0 +1,152 @@
|
|||
package net.vhati.modmanager.ui;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Cursor;
|
||||
import java.awt.Desktop;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.net.URI;
|
||||
import java.util.HashMap;
|
||||
import javax.swing.JScrollPane;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.event.MouseInputAdapter;
|
||||
import javax.swing.text.AttributeSet;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.SimpleAttributeSet;
|
||||
import javax.swing.text.StyleConstants;
|
||||
import javax.swing.text.StyledDocument;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
public class ModInfoArea extends JScrollPane {
|
||||
|
||||
private static final Logger log = LogManager.getLogger(ModInfoArea.class);
|
||||
|
||||
private static final String HYPERLINK_TARGET = "hyperlink-target";
|
||||
|
||||
private JTextPane textPane;
|
||||
private StyledDocument doc;
|
||||
private HashMap<String,SimpleAttributeSet> attrMap = new HashMap<String,SimpleAttributeSet>();
|
||||
|
||||
|
||||
public ModInfoArea() {
|
||||
super();
|
||||
|
||||
textPane = new JTextPane();
|
||||
textPane.setEditable( false );
|
||||
|
||||
doc = textPane.getStyledDocument();
|
||||
|
||||
SimpleAttributeSet tmpAttr = new SimpleAttributeSet();
|
||||
StyleConstants.setFontFamily( tmpAttr, "Monospaced" );
|
||||
StyleConstants.setFontSize( tmpAttr, 12 );
|
||||
attrMap.put( "regular", tmpAttr );
|
||||
|
||||
tmpAttr = new SimpleAttributeSet();
|
||||
StyleConstants.setFontFamily( tmpAttr, "SansSerif" );
|
||||
StyleConstants.setFontSize( tmpAttr, 24 );
|
||||
StyleConstants.setBold( tmpAttr, true );
|
||||
attrMap.put( "title", tmpAttr );
|
||||
|
||||
tmpAttr = new SimpleAttributeSet();
|
||||
StyleConstants.setFontFamily( tmpAttr, "Monospaced" );
|
||||
StyleConstants.setFontSize( tmpAttr, 12 );
|
||||
StyleConstants.setForeground( tmpAttr, Color.BLUE );
|
||||
StyleConstants.setUnderline( tmpAttr, true );
|
||||
attrMap.put( "hyperlink", tmpAttr );
|
||||
|
||||
MouseInputAdapter hyperlinkListener = new MouseInputAdapter() {
|
||||
@Override
|
||||
public void mouseClicked( MouseEvent e ) {
|
||||
AttributeSet tmpAttr = doc.getCharacterElement( textPane.viewToModel(e.getPoint()) ).getAttributes();
|
||||
Object targetObj = tmpAttr.getAttribute( 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseMoved( MouseEvent e ) {
|
||||
AttributeSet tmpAttr = doc.getCharacterElement( textPane.viewToModel(e.getPoint()) ).getAttributes();
|
||||
if ( tmpAttr.getAttribute( HYPERLINK_TARGET ) != null ) {
|
||||
textPane.setCursor( new Cursor(Cursor.HAND_CURSOR) );
|
||||
} else {
|
||||
textPane.setCursor( new Cursor(Cursor.DEFAULT_CURSOR) );
|
||||
}
|
||||
}
|
||||
};
|
||||
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 ) {
|
||||
try {
|
||||
doc.remove( 0, doc.getLength() );
|
||||
doc.insertString( doc.getLength(), title +"\n", attrMap.get("title") );
|
||||
|
||||
boolean first = true;
|
||||
if ( author != null ) {
|
||||
doc.insertString( doc.getLength(), String.format("%sby %s", (first ? "" : " "), author), attrMap.get("regular") );
|
||||
first = false;
|
||||
}
|
||||
if ( version != null ) {
|
||||
doc.insertString( doc.getLength(), String.format("%s(version %s)", (first ? "" : " "), version), attrMap.get("regular") );
|
||||
first = false;
|
||||
}
|
||||
if ( !first ) {
|
||||
doc.insertString( doc.getLength(), "\n", attrMap.get("regular") );
|
||||
}
|
||||
|
||||
if ( url != null ) {
|
||||
SimpleAttributeSet tmpAttr;
|
||||
doc.insertString( doc.getLength(), "Website: ", attrMap.get("regular") );
|
||||
|
||||
boolean browseWorks = false;
|
||||
Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null;
|
||||
if ( desktop != null && desktop.isSupported(Desktop.Action.BROWSE) ) {
|
||||
browseWorks = true;
|
||||
}
|
||||
|
||||
if ( browseWorks && url.matches("^(?:https?|ftp)://.*") ) {
|
||||
tmpAttr = new SimpleAttributeSet( attrMap.get("hyperlink") );
|
||||
tmpAttr.addAttribute( HYPERLINK_TARGET, url );
|
||||
doc.insertString( doc.getLength(), "Link", tmpAttr );
|
||||
} else {
|
||||
tmpAttr = new SimpleAttributeSet( attrMap.get("regular") );
|
||||
doc.insertString( doc.getLength(), url, tmpAttr );
|
||||
}
|
||||
|
||||
doc.insertString( doc.getLength(), "\n", attrMap.get("regular") );
|
||||
}
|
||||
|
||||
doc.insertString( doc.getLength(), "\n", attrMap.get("regular") );
|
||||
|
||||
if ( body != null ) {
|
||||
doc.insertString( doc.getLength(), body, attrMap.get("regular") );
|
||||
}
|
||||
}
|
||||
catch ( BadLocationException e) {
|
||||
log.error( e );
|
||||
}
|
||||
|
||||
textPane.setCaretPosition(0);
|
||||
}
|
||||
}
|
182
src/main/java/net/vhati/modmanager/ui/ModPatchDialog.java
Normal file
182
src/main/java/net/vhati/modmanager/ui/ModPatchDialog.java
Normal file
|
@ -0,0 +1,182 @@
|
|||
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.JTextArea;
|
||||
import javax.swing.SwingUtilities;
|
||||
|
||||
import net.vhati.modmanager.core.ModPatchObserver;
|
||||
|
||||
|
||||
public class ModPatchDialog extends JDialog implements ActionListener, ModPatchObserver {
|
||||
|
||||
private JProgressBar progressBar;
|
||||
private JTextArea statusArea;
|
||||
private JButton continueBtn;
|
||||
|
||||
private boolean done = false;
|
||||
private boolean patchingSucceeded = false;
|
||||
private Runnable successTask = null;
|
||||
|
||||
|
||||
public ModPatchDialog( Frame owner ) {
|
||||
super( owner, "Patching...", true );
|
||||
this.setDefaultCloseOperation( JDialog.DO_NOTHING_ON_CLOSE );
|
||||
|
||||
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();
|
||||
statusArea.setBorder( BorderFactory.createEtchedBorder() );
|
||||
statusArea.setLineWrap( true );
|
||||
statusArea.setWrapStyleWord( true );
|
||||
statusArea.setEditable( false );
|
||||
|
||||
JPanel statusHolder = new JPanel( new BorderLayout() );
|
||||
statusHolder.setBorder( BorderFactory.createEmptyBorder( 15, 15, 15, 15 ) );
|
||||
statusHolder.add( statusArea );
|
||||
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.setSize( 400, 160 );
|
||||
this.setLocationRelativeTo( owner );
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void actionPerformed( ActionEvent e ) {
|
||||
Object source = e.getSource();
|
||||
|
||||
if ( source == continueBtn ) {
|
||||
this.setVisible( false );
|
||||
this.dispose();
|
||||
|
||||
if ( done && patchingSucceeded && successTask != null ) {
|
||||
successTask.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void setStatusText( String message ) {
|
||||
statusArea.setText( message != null ? message : "..." );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
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 );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Non-specific activity.
|
||||
*
|
||||
* @param message a string, or null
|
||||
*/
|
||||
@Override
|
||||
public void patchingStatus( final String message ) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setStatusText( message != null ? message : "..." );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* A mod is about to be processed.
|
||||
*/
|
||||
@Override
|
||||
public void patchingMod( final File modFile ) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setStatusText( String.format( "Installing mod \"%s\"...", modFile.getName() ) );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Patching ended.
|
||||
*
|
||||
* If anything went wrong, e may be non-null.
|
||||
*/
|
||||
@Override
|
||||
public void patchingEnded( final boolean success, final Exception e ) {
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if ( success )
|
||||
setStatusText( "Patching completed." );
|
||||
else
|
||||
setStatusText( String.format( "Patching failed: %s", e ) );
|
||||
|
||||
done = true;
|
||||
patchingSucceeded = success;
|
||||
|
||||
continueBtn.setEnabled( true );
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets a runnable to trigger after patching successfully.
|
||||
*/
|
||||
public void setSuccessTask( Runnable r ) {
|
||||
successTask = r;
|
||||
}
|
||||
}
|
9
src/main/java/net/vhati/modmanager/ui/Reorderable.java
Normal file
9
src/main/java/net/vhati/modmanager/ui/Reorderable.java
Normal file
|
@ -0,0 +1,9 @@
|
|||
package net.vhati.modmanager.ui;
|
||||
|
||||
|
||||
public interface Reorderable {
|
||||
/**
|
||||
* Moves an element at fromIndex to toIndex.
|
||||
*/
|
||||
public void reorder( int fromIndex, int toIndex );
|
||||
}
|
6
src/main/java/net/vhati/modmanager/ui/Statusbar.java
Normal file
6
src/main/java/net/vhati/modmanager/ui/Statusbar.java
Normal file
|
@ -0,0 +1,6 @@
|
|||
package net.vhati.modmanager.ui;
|
||||
|
||||
|
||||
public interface Statusbar {
|
||||
public void setStatusText( String text );
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
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 {
|
||||
private Statusbar bar = null;
|
||||
private 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( "" );
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package net.vhati.modmanager.ui;
|
||||
|
||||
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.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/**
|
||||
* 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 = LogManager.getLogger(TableRowTransferHandler.class);
|
||||
|
||||
private DataFlavor localIntegerFlavor = null;
|
||||
|
||||
private JTable table = null;
|
||||
|
||||
|
||||
public TableRowTransferHandler( JTable table ) {
|
||||
if ( table.getModel() instanceof Reorderable == false ) {
|
||||
throw new IllegalArgumentException( "The tableModel doesn't implement Reorderable." );
|
||||
}
|
||||
this.table = table;
|
||||
|
||||
try {
|
||||
localIntegerFlavor = new DataFlavor( DataFlavor.javaJVMLocalObjectMimeType + ";class=\""+ Integer.class.getName() +"\"" );
|
||||
}
|
||||
catch ( ClassNotFoundException e ) {
|
||||
log.error( 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( 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 );
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue