diff --git a/src/main/java/net/vhati/modmanager/core/HashObserver.java b/src/main/java/net/vhati/modmanager/core/HashObserver.java index e8079e4..2fa67c8 100644 --- a/src/main/java/net/vhati/modmanager/core/HashObserver.java +++ b/src/main/java/net/vhati/modmanager/core/HashObserver.java @@ -4,5 +4,14 @@ import java.io.File; public interface HashObserver { + + /** + * A file's hash has been calculated. + */ public void hashCalculated( File f, String hash ); + + /** + * Hashing ended. + */ + public void hashingEnded(); } diff --git a/src/main/java/net/vhati/modmanager/core/HashThread.java b/src/main/java/net/vhati/modmanager/core/HashThread.java index 1bafe93..eee7e23 100644 --- a/src/main/java/net/vhati/modmanager/core/HashThread.java +++ b/src/main/java/net/vhati/modmanager/core/HashThread.java @@ -42,6 +42,7 @@ public class HashThread extends Thread { } log.info( "Background hashing finished." ); + hashObserver.hashingEnded(); } diff --git a/src/main/java/net/vhati/modmanager/ui/ChecklistTableModel.java b/src/main/java/net/vhati/modmanager/ui/ChecklistTableModel.java index f61f25c..7eee528 100644 --- a/src/main/java/net/vhati/modmanager/ui/ChecklistTableModel.java +++ b/src/main/java/net/vhati/modmanager/ui/ChecklistTableModel.java @@ -41,6 +41,11 @@ public class ChecklistTableModel extends AbstractTableModel implements Reorde fireTableRowsDeleted( row, row ); } + public void removeAllItems() { + rowsList.clear(); + fireTableDataChanged(); + } + @SuppressWarnings("unchecked") public T getItem( int row ) { return (T)rowsList.get(row).get(DATA_PAYLOAD); diff --git a/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java b/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java index f32209b..56b2012 100644 --- a/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java +++ b/src/main/java/net/vhati/modmanager/ui/ManagerFrame.java @@ -37,9 +37,13 @@ import javax.swing.DropMode; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JSeparator; import javax.swing.JSplitPane; import javax.swing.JTable; import javax.swing.JTextArea; @@ -98,11 +102,17 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver private ChecklistTableModel localModsTableModel; private JTable localModsTable; + private JMenuBar menubar; + private JMenu fileMenu; + private JMenuItem rescanMenuItem; + private JMenuItem exitMenuItem; + private JMenu helpMenu; + private JMenuItem aboutMenuItem; + private JButton patchBtn; private JButton toggleAllBtn; private JButton validateBtn; private JButton modsFolderBtn; - private JButton aboutBtn; private ModInfoArea infoArea; @@ -173,15 +183,9 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver modsFolderBtn.setEnabled( Desktop.isDesktopSupported() ); modActionsPanel.add( modsFolderBtn ); - 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, modsFolderBtn, aboutBtn}; + JButton[] actionBtns = new JButton[] {patchBtn, toggleAllBtn, validateBtn, modsFolderBtn }; int actionBtnWidth = Integer.MIN_VALUE; int actionBtnHeight = Integer.MIN_VALUE; for ( JButton btn : actionBtns ) { @@ -222,8 +226,7 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver sortedMods.add( localModsTableModel.getItem(i) ); } saveModOrder( sortedMods ); - ManagerFrame.this.setVisible( false ); - ManagerFrame.this.dispose(); + System.exit( 0 ); } }); @@ -280,6 +283,26 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver localModsTable.setDropMode( DropMode.INSERT ); // Drop between rows, not on them. localModsTable.setDragEnabled( true ); + menubar = new JMenuBar(); + fileMenu = new JMenu( "File" ); + rescanMenuItem = new JMenuItem( "Re-Scan mods/" ); + rescanMenuItem.addMouseListener( new StatusbarMouseListener( this, "Check the mods/ folder for new files." ) ); + rescanMenuItem.addActionListener(this); + fileMenu.add( rescanMenuItem ); + fileMenu.add( new JSeparator() ); + exitMenuItem = new JMenuItem( "Exit" ); + exitMenuItem.addMouseListener( new StatusbarMouseListener( this, "Exit this application." ) ); + exitMenuItem.addActionListener(this); + fileMenu.add( exitMenuItem ); + menubar.add( fileMenu ); + helpMenu = new JMenu( "Help" ); + aboutMenuItem = new JMenuItem( "About" ); + aboutMenuItem.addMouseListener( new StatusbarMouseListener( this, "Show info about this application." ) ); + aboutMenuItem.addActionListener(this); + helpMenu.add( aboutMenuItem ); + menubar.add( helpMenu ); + this.setJMenuBar( menubar ); + this.setContentPane( contentPane ); this.pack(); this.setMinimumSize( new Dimension( 300, modActionsPanel.getPreferredSize().height+90 ) ); @@ -293,34 +316,9 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver * 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; - if ( config.getProperty( "allow_zip", "false" ).equals( "true" ) ) { - if ( f.getName().endsWith(".zip") ) return true; - } - } - return false; - } - }); - - List unsortedMods = new ArrayList(); - for ( File f : modFiles ) { - ModFileInfo modFileInfo = new ModFileInfo( f ); - unsortedMods.add( modFileInfo ); - } - - List sortedMods = loadModOrder( unsortedMods ); - for ( ModFileInfo modFileInfo : sortedMods ) { - localModsTableModel.addItem( modFileInfo ); - } - - HashThread hashThread = new HashThread( modFiles, this ); - hashThread.setDaemon( true ); - hashThread.start(); + List preferredOrder = loadModOrder(); + rescanMods( preferredOrder ); boolean needNewCatalog = false; @@ -382,31 +380,46 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver /** - * Reads modorder.txt and returns a mod list in that order. + * Returns a mod list with names sorted in a preferred order. * - * Mods not mentioned in the text appear at the end, alphabetically. - * If an error occurs, an alphabetized list is returned. + * Mods not mentioned in the name list appear at the end, alphabetically. */ - private List loadModOrder( List unsortedMods ) { + private List reorderMods( List unsortedMods, List preferredOrder ) { List sortedMods = new ArrayList(); List availableMods = new ArrayList( unsortedMods ); Collections.sort( availableMods ); + if ( preferredOrder != null ) { + for ( String name : preferredOrder ) { + Iterator it = availableMods.iterator(); + while ( it.hasNext() ) { + ModFileInfo modFileInfo = it.next(); + if ( modFileInfo.getName().equals( name ) ) { + it.remove(); + sortedMods.add( modFileInfo ); + break; + } + } + } + } + sortedMods.addAll( availableMods ); + + return sortedMods; + } + + /** + * Reads modorder.txt and returns a list of mod names in preferred order. + */ + private List loadModOrder() { + List result = new ArrayList(); + 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 it = availableMods.iterator(); - while ( it.hasNext() ) { - ModFileInfo modFileInfo = it.next(); - if ( modFileInfo.getName().equals(line) ) { - it.remove(); - sortedMods.add( modFileInfo ); - break; - } - } + result.add( line ); } } catch ( FileNotFoundException e ) { @@ -418,9 +431,8 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver try {if (is != null) is.close();} catch (Exception e) {} } - sortedMods.addAll( availableMods ); - return sortedMods; + return result; } private void saveModOrder( List sortedMods ) { @@ -444,6 +456,35 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver } } + /** + * Clears and syncs the mods list with mods/ dir, then starts a new hash thread. + */ + private void rescanMods( List preferredOrder ) { + if ( rescanMenuItem.isEnabled() == false ) return; + rescanMenuItem.setEnabled( false ); + + modFileHashes.clear(); + localModsTableModel.removeAllItems(); + + boolean allowZip = config.getProperty( "allow_zip", "false" ).equals( "true" ); + File[] modFiles = modsDir.listFiles( new ModFileFilter( allowZip ) ); + + List unsortedMods = new ArrayList(); + for ( File f : modFiles ) { + ModFileInfo modFileInfo = new ModFileInfo( f ); + unsortedMods.add( modFileInfo ); + } + + List sortedMods = reorderMods( unsortedMods, preferredOrder ); + for ( ModFileInfo modFileInfo : sortedMods ) { + localModsTableModel.addItem( modFileInfo ); + } + + HashThread hashThread = new HashThread( modFiles, this ); + hashThread.setDaemon( true ); + hashThread.start(); + } + public void showAboutInfo() { String body = ""; @@ -495,6 +536,11 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver } } + public void exitApp() { + this.setVisible( false ); + this.dispose(); + } + @Override public void setStatusText( String text ) { @@ -593,7 +639,23 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver log.error( "Error opening mods/ folder.", f ); } } - else if ( source == aboutBtn ) { + else if ( source == rescanMenuItem ) { + setStatusText( "" ); + if ( rescanMenuItem.isEnabled() == false ) return; + + List preferredOrder = new ArrayList(); + + for ( int i=0; i < localModsTableModel.getRowCount(); i++ ) { + preferredOrder.add( localModsTableModel.getItem(i).getName() ); + } + rescanMods( preferredOrder ); + } + else if ( source == exitMenuItem ) { + setStatusText( "" ); + exitApp(); + } + else if ( source == aboutMenuItem ) { + setStatusText( "" ); showAboutInfo(); } } @@ -609,6 +671,16 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver }); } + @Override + public void hashingEnded() { + SwingUtilities.invokeLater( new Runnable() { + @Override + public void run() { + rescanMenuItem.setEnabled( true ); + } + }); + } + private class SpawnGameTask implements Runnable { @@ -628,10 +700,32 @@ public class ManagerFrame extends JFrame implements ActionListener, HashObserver } catch ( Exception e ) { log.error( "Error launching FTL.", e ); } - System.exit( 0 ); + exitApp(); } } } } } + + + + private static class ModFileFilter implements FileFilter { + boolean allowZip; + + public ModFileFilter( boolean allowZip ) { + this.allowZip = allowZip; + } + + @Override + public boolean accept( File f ) { + if ( f.isFile() ) { + if ( f.getName().endsWith(".ftl") ) return true; + + if ( allowZip ) { + if ( f.getName().endsWith(".zip") ) return true; + } + } + return false; + } + } }