2024-04-10 03:35:27 -04:00
/ *
* Firestar Mod Manager
* Copyright ( C ) 2024 bonkmaykr
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program 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 General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see https : //www.gnu.org/licenses/.
* /
2024-04-21 14:37:17 -04:00
import javax.imageio.ImageIO ;
2024-04-10 04:49:21 -04:00
import javax.swing.* ;
2024-04-19 19:06:52 -04:00
import javax.swing.event.ListSelectionListener ;
2024-04-12 00:26:26 -04:00
import java.awt.* ;
2024-04-12 11:21:51 -04:00
import java.awt.event.ActionEvent ;
import java.awt.event.ActionListener ;
2024-04-21 14:37:17 -04:00
import java.awt.image.BufferedImage ;
2024-04-19 19:06:52 -04:00
import java.io.File ;
2024-04-21 14:37:17 -04:00
import java.io.IOException ;
2024-04-19 19:06:52 -04:00
import java.math.RoundingMode ;
2024-05-07 01:47:43 -04:00
import java.nio.file.* ;
2024-04-19 19:06:52 -04:00
import java.text.DecimalFormat ;
2024-05-07 05:26:40 -04:00
import java.util.Arrays ;
import java.util.regex.Pattern ;
2024-05-07 01:47:43 -04:00
import net.lingala.zip4j.* ;
2024-05-07 06:26:54 -04:00
import net.lingala.zip4j.exception.ZipException ;
import org.json.JSONObject ;
2024-04-12 11:21:51 -04:00
2024-04-12 00:26:26 -04:00
import static javax.swing.WindowConstants.EXIT_ON_CLOSE ;
2024-04-10 04:49:21 -04:00
2024-04-12 11:21:51 -04:00
public class MissPiggy implements ActionListener {
2024-04-21 14:37:17 -04:00
BufferedImage windowIcon ;
2024-04-12 00:26:26 -04:00
JFrame frame = new JFrame ( ) ;
JPanel frameContainer ;
JPanel actionsContainer ;
JPanel descriptionContainer ;
2024-04-12 11:21:51 -04:00
//JPanel menuBarContainerPanel = new JPanel();
public JMenuBar menuBar ;
public JMenu fileMenu ;
public JMenu toolsMenu ;
public JMenu helpMenu ;
//JMenuItem menuItem;
2024-04-12 00:26:26 -04:00
JScrollPane modListScrollContainer ;
2024-04-19 19:06:52 -04:00
public JList < String > modList ;
2024-04-12 00:26:26 -04:00
private JButton toggleButton ;
private JButton moveUpButton ;
private JButton deleteButton1 ;
private JButton moveDownButton ;
private JButton optionsButton ;
private JButton importButton ;
2024-04-12 11:21:51 -04:00
private JButton deployButton ;
2024-04-19 19:06:52 -04:00
private JTextPane descriptionField ;
2024-04-12 00:26:26 -04:00
private int selectedItem ;
2024-05-07 01:47:43 -04:00
public String priorityList ;
2024-04-12 00:26:26 -04:00
// Initialize the main window
public void Action ( /*Main entryPoint*/ ) {
2024-05-07 01:47:43 -04:00
// populate menu bar
2024-04-12 11:21:51 -04:00
menuBar = new JMenuBar ( ) ;
fileMenu = new JMenu ( " File " ) ;
toolsMenu = new JMenu ( " Tools " ) ;
helpMenu = new JMenu ( " Help " ) ;
fileMenu . add ( new JMenuItem ( " Deploy All Mods " ) ) ;
fileMenu . add ( new JMenuItem ( " Import Mod... " ) ) ;
fileMenu . add ( new JMenuItem ( " Remove All " ) ) ;
fileMenu . add ( new JSeparator ( ) ) ;
fileMenu . add ( new JMenuItem ( " Options " ) ) ;
fileMenu . add ( new JMenuItem ( " Quit " ) ) ;
toolsMenu . add ( new JMenuItem ( " Edit Metadata " ) ) ; // disabled if a mod is not selected from the list
toolsMenu . add ( new JMenuItem ( " Generate New Mod from Folder... " ) ) ;
2024-05-07 01:47:43 -04:00
toolsMenu . add ( new JMenuItem ( " Create Soundtrack Mod... " ) ) ;
//toolsMenu.add(new JMenuItem("Download Mod from URL")); // TODO: implement. move option to File menu. should be ez
2024-04-12 11:21:51 -04:00
helpMenu . add ( new JMenuItem ( " About Firestar " ) ) ;
menuBar . add ( fileMenu ) ;
menuBar . add ( toolsMenu ) ;
menuBar . add ( helpMenu ) ;
menuBar . setVisible ( true ) ;
frame . setJMenuBar ( menuBar ) ;
2024-04-12 00:26:26 -04:00
frame . add ( frameContainer ) ; // initialize window contents -- will be handled by IntelliJ IDEA
2024-05-07 06:26:54 -04:00
InitializeModListStructure ( ) ;
2024-04-12 11:21:51 -04:00
InitializeModListInGUI ( ) ; // present mod list
2024-04-12 11:31:38 -04:00
fileMenu . getItem ( 0 ) . addActionListener ( this ) ;
fileMenu . getItem ( 1 ) . addActionListener ( this ) ;
fileMenu . getItem ( 2 ) . addActionListener ( this ) ;
fileMenu . getItem ( 4 ) . addActionListener ( this ) ;
2024-04-12 11:21:51 -04:00
fileMenu . getItem ( 5 ) . addActionListener ( this ) ;
2024-04-12 11:31:38 -04:00
toolsMenu . getItem ( 0 ) . addActionListener ( this ) ;
toolsMenu . getItem ( 1 ) . addActionListener ( this ) ;
helpMenu . getItem ( 0 ) . addActionListener ( this ) ;
2024-04-12 11:21:51 -04:00
2024-04-19 19:06:52 -04:00
descriptionField . getDocument ( ) . putProperty ( " filterNewlines " , Boolean . FALSE ) ;
modList . addListSelectionListener ( e - > {
String authorDisplay ;
2024-05-07 06:26:54 -04:00
File pathReference = new File ( System . getProperty ( " user.home " ) + " /.firestar/mods/ " + Main . Mods . get ( modList . getSelectedIndex ( ) ) . path ) ;
2024-04-19 19:06:52 -04:00
DecimalFormat df = new DecimalFormat ( " ##.## " ) ;
df . setRoundingMode ( RoundingMode . UP ) ;
float modFileSize = pathReference . length ( ) ; //precise units
String modFileSizeStr = String . valueOf ( modFileSize ) ;
2024-05-07 06:26:54 -04:00
String modFileSizeUnits = " bytes " ; //todo: don't show decimals for bytes
2024-04-19 19:06:52 -04:00
if ( pathReference . length ( ) > = 1024 ) {
modFileSizeStr = String . valueOf ( df . format ( modFileSize / 1024 ) ) ;
modFileSizeUnits = " Kilobytes " ;
}
if ( pathReference . length ( ) > = 1024 * 1024 ) {
modFileSizeStr = String . valueOf ( df . format ( modFileSize / ( 1024 * 1024 ) ) ) ;
modFileSizeUnits = " Megabytes " ;
}
if ( pathReference . length ( ) > = 1024 * 1024 * 1024 ) {
modFileSizeStr = String . valueOf ( df . format ( modFileSize / ( 1024 * 1024 * 1024 ) ) ) ;
modFileSizeUnits = " Gigabytes " ;
}
if ( Main . Mods . get ( modList . getSelectedIndex ( ) ) . author = = null ) {
authorDisplay = " an Unknown Author " ;
} else {
authorDisplay = Main . Mods . get ( modList . getSelectedIndex ( ) ) . author ;
}
descriptionField . setText (
" \" " + Main . Mods . get ( modList . getSelectedIndex ( ) ) . friendlyName + " \" \ n " +
" by " + authorDisplay + " \ n \ n " +
" Version " + Main . Mods . get ( modList . getSelectedIndex ( ) ) . version + " \ n " +
2024-05-07 00:36:41 -04:00
modFileSizeStr + " " + modFileSizeUnits + " in size " +
" \ n \ n " + Main . Mods . get ( modList . getSelectedIndex ( ) ) . description
2024-04-19 19:06:52 -04:00
) ; } ) ;
2024-04-12 11:21:51 -04:00
// display window
2024-04-21 14:37:17 -04:00
try {
windowIcon = ImageIO . read ( new File ( System . getProperty ( " user.dir " ) + " /resources/titleIcon.png " ) ) ;
frame . setIconImage ( windowIcon ) ;
} catch ( IOException e ) {
System . out . println ( " ERROR: Failed to find /resources/titleIcon.png. Window will not have an icon. " ) ;
}
2024-04-12 00:26:26 -04:00
frame . setSize ( 800 , 600 ) ; // 1280 800
2024-04-19 19:06:52 -04:00
frame . setMinimumSize ( new Dimension ( 640 , 480 ) ) ;
2024-04-12 00:26:26 -04:00
frame . setTitle ( " Firestar Mod Manager " ) ;
frame . setResizable ( true ) ;
frame . setDefaultCloseOperation ( EXIT_ON_CLOSE ) ;
frame . setLayout ( new GridLayout ( ) ) ;
2024-04-13 11:04:35 -04:00
frame . setLocationRelativeTo ( null ) ;
2024-04-12 00:26:26 -04:00
frame . setVisible ( true ) ;
}
2024-05-07 06:26:54 -04:00
public void InitializeModListStructure ( ) {
2024-04-12 00:26:26 -04:00
// cleanup
2024-05-07 06:26:54 -04:00
Main . Mods . clear ( ) ;
2024-04-12 00:26:26 -04:00
2024-05-07 06:26:54 -04:00
// get current list of mods from file
// todo: rewrite when modpacks/playlists are added
2024-05-07 01:47:43 -04:00
try {
2024-05-07 05:26:40 -04:00
priorityList = new String ( Files . readAllBytes ( Paths . get ( System . getProperty ( " user.home " ) + " /.firestar/mods/index " ) ) ) ;
2024-05-07 01:47:43 -04:00
} catch ( IOException e ) {
2024-05-07 05:26:40 -04:00
File priorityListFileHandle = new File ( System . getProperty ( " user.home " ) + " /.firestar/mods/index " ) ;
new File ( System . getProperty ( " user.home " ) + " /.firestar/mods/ " ) . mkdirs ( ) ;
if ( ! priorityListFileHandle . isFile ( ) ) {
try {
priorityListFileHandle . createNewFile ( ) ;
} catch ( IOException ex ) {
throw new RuntimeException ( ex ) ;
}
}
2024-05-07 01:47:43 -04:00
priorityList = " " ;
}
2024-05-07 05:26:40 -04:00
// initialize data structures from each list entry
String [ ] pListArray = priorityList . split ( " \ n " ) ;
Arrays . sort ( pListArray ) ;
2024-05-07 06:26:54 -04:00
System . out . println ( " Initializing modList from file with length of " + pListArray . length + " units " ) ; //debug
2024-05-07 05:26:40 -04:00
for ( String s : pListArray ) {
2024-05-07 06:26:54 -04:00
/ *
Do nothing if the index number is not valid .
there probably is not a practical reason to do this , but I want to eliminate any undefined behaviors while we ' re here .
we ' ll also eliminate any syntax errors caused by the lack of a = sign
* /
if ( s . split ( " = " ) [ 0 ] . matches ( " [0-9]+=* " ) ) {
//append mod to list from substring
Main . Mod m = new Main ( ) . new Mod ( ) ;
m . path = s . substring ( s . indexOf ( " = " ) + 1 ) ;
System . out . println ( " found file " + m . path ) ;
//get json metadata from zip comment
JSONObject metadata ;
try {
metadata = new JSONObject ( new ZipFile ( System . getProperty ( " user.home " ) + " /.firestar/mods/ " + m . path ) . getComment ( ) ) ;
} catch ( ZipException e ) {
throw new RuntimeException ( e ) ; //todo: fault tolerance
}
if ( metadata . has ( " friendlyName " ) ) { m . friendlyName = metadata . get ( " friendlyName " ) . toString ( ) ; } else { m . friendlyName = m . path ; }
if ( metadata . has ( " description " ) ) { m . description = metadata . get ( " description " ) . toString ( ) ; }
if ( metadata . has ( " version " ) ) { m . version = Integer . parseInt ( metadata . get ( " version " ) . toString ( ) ) ; }
if ( metadata . has ( " author " ) ) { m . author = metadata . get ( " author " ) . toString ( ) ; }
if ( metadata . has ( " loaderversion " ) ) { m . loaderversion = Integer . parseInt ( metadata . get ( " loaderversion " ) . toString ( ) ) ; }
if ( metadata . has ( " game " ) ) { m . game = metadata . get ( " game " ) . toString ( ) ; }
//send to list
Main . Mods . add ( m ) ;
2024-05-07 05:26:40 -04:00
}
}
2024-05-07 06:26:54 -04:00
}
public void InitializeModListInGUI ( ) { // i really wanted this to be "lights, camera, action" but the code organizing kept getting stupider and stupider so i gave up
// cleanup
descriptionField . setText ( " Select a mod from the list on the right to view more details, or to make changes to your installation. " ) ;
modList . clearSelection ( ) ;
modList . removeAll ( ) ;
modList . setVisibleRowCount ( Main . Mods . size ( ) ) ;
modList . setSelectionMode ( ListSelectionModel . SINGLE_SELECTION ) ;
2024-05-07 05:26:40 -04:00
2024-04-12 00:26:26 -04:00
// add text entry for each
int i = 0 ;
2024-04-19 18:00:20 -04:00
/*JLabel[]*/ String [ ] contents = new String [ Main . Mods . size ( ) ] ;
2024-04-21 14:37:17 -04:00
System . out . println ( " Initializing modList to GUI with length of " + Main . Mods . size ( ) + " units " ) ; //debug
2024-04-12 00:26:26 -04:00
while ( i < Main . Mods . size ( ) ) {
2024-04-19 18:00:20 -04:00
contents [ i ] = Main . Mods . get ( i ) . friendlyName ;
2024-04-12 00:26:26 -04:00
//debug
2024-04-19 19:06:52 -04:00
String authorDisplay ;
if ( Main . Mods . get ( i ) . author = = null ) { authorDisplay = " Anonymous " ; } else { authorDisplay = " \" " + Main . Mods . get ( i ) . author + " \" " ; }
System . out . println ( " Added " + Main . Mods . get ( i ) . friendlyName + " by " + authorDisplay ) ;
2024-04-10 04:49:21 -04:00
2024-04-12 00:26:26 -04:00
i + + ;
}
2024-04-19 18:00:20 -04:00
modList . setListData ( contents ) ;
2024-04-10 03:35:27 -04:00
}
2024-04-12 11:21:51 -04:00
2024-04-19 19:06:52 -04:00
private ListSelectionListener whenItemSelected ( ) {
return null ;
}
2024-04-12 11:21:51 -04:00
@Override
public void actionPerformed ( ActionEvent actionEvent ) {
2024-04-12 11:31:38 -04:00
if ( actionEvent . getSource ( ) = = fileMenu . getItem ( 5 ) ) { System . exit ( 0 ) ; } else
if ( actionEvent . getSource ( ) = = fileMenu . getItem ( 0 ) ) { deployModGUI ( ) ; } else
if ( actionEvent . getSource ( ) = = fileMenu . getItem ( 1 ) ) { importModGUI ( ) ; } else
if ( actionEvent . getSource ( ) = = fileMenu . getItem ( 2 ) ) { removeAllGUI ( ) ; } else
2024-04-13 11:04:35 -04:00
if ( actionEvent . getSource ( ) = = fileMenu . getItem ( 4 ) ) { optionsGUI ( ) ; } else
if ( actionEvent . getSource ( ) = = helpMenu . getItem ( 0 ) ) { new Rowlf ( ) . displayAboutScreen ( ) ; }
2024-04-12 11:31:38 -04:00
}
// Will likely split the below functions into separate classes to work with intellij GUI designer.
public void deployModGUI ( ) {
// todo dialog that monitors psarc progress, handles file transfers, etc
// could prevent closing program during this process? (may also be unnecessary)
}
public void importModGUI ( ) {
// todo call system shell to request file picker
}
public void removeAllGUI ( ) {
// todo warning dialog that nukes list when Yes is clicked
}
public void optionsGUI ( ) {
// todo settings page w/ reset switch
}
public void generatorGUI ( ) {
// todo mod packer
}
public void metaEditorGUI ( ) {
// todo tag editor
}
public void aboutGUI ( ) {
// todo about page
2024-04-12 11:21:51 -04:00
}
2024-04-10 03:35:27 -04:00
}