/**
* Copyright 2012 Andrew Okin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package forkk.multimc.gui;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.InvalidParameterException;
import java.util.logging.Logger;
import javax.swing.Box;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import forkk.multimc.data.FileDrop;
import forkk.multimc.data.FileDrop.Listener;
import forkk.multimc.data.Instance;
import forkk.multimc.data.InstanceListModel;
import forkk.multimc.data.Version;
import forkk.multimc.data.exceptions.InstanceInitException;
import forkk.multimc.data.exceptions.InstanceSaveException;
import forkk.multimc.settings.AppSettings;
import forkk.multimc.task.Downloader;
import forkk.multimc.task.JarBuilder;
import forkk.multimc.task.Task;
import forkk.multimc.task.TaskAdapter;
import forkk.multimc.task.UpdateCheck;
import forkk.multimc.update.UpdateFile;
public class SelectionWindow implements ActionListener, Task.TaskListener
{
private static String MainWindowTitle = "MultiMC";
/**
* The web page that is opened for manual updates.
*/
@SuppressWarnings("unused")
private static String updatePage = "http://www.tinyurl.com/multiplemc";
public static URL getLauncherDownloadURL()
{
try
{
return new URL("https://s3.amazonaws.com/MinecraftDownload/launcher/minecraft.jar");
} catch (MalformedURLException e)
{
e.printStackTrace();
mainLog.fine("The launcher URL was reported invalid. Please report " +
"this bug.");
System.exit(0);
return null;
}
}
private static boolean updateOnExit = false;
private static boolean restartAfterUpdate = false;
private JFrame mainFrame;
public static final File latestUpdateTemp = new File("LatestVersion.jar");
public static final String reportBugURL = "https://github.com/Forkk/MultiMC/issues";
/**
* Launch the application.
*/
public static void main(String[] args)
{
try
{
if (latestUpdateTemp.exists())
latestUpdateTemp.delete();
// A list of preferred L&Fs to use
final String[] themes = new String[] { "Nimbus" };
boolean setTheme = false;
for (String theme : themes)
{
for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels())
{
if (info.getName().equals(theme))
{
UIManager.setLookAndFeel(info.getClassName());
setTheme = true;
break;
}
}
}
if (!setTheme)
{
mainLog.warning("Could not find any theme to use, using system instead.");
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
}
} catch (Throwable e)
{
e.printStackTrace();
}
EventQueue.invokeLater(new Runnable()
{
public void run()
{
if (latestUpdateTemp.exists())
latestUpdateTemp.delete();
try
{
SelectionWindow window = new SelectionWindow();
window.mainFrame.setVisible(true);
window.mainFrame.addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent ev)
{
AppSettings.saveSettings();
if (updateOnExit && latestUpdateTemp.exists())
{
System.out.println("Updating");
String options = "";
if (restartAfterUpdate)
options += "r";
try
{
ProcessBuilder updateProcBuilder =
new ProcessBuilder("java", "-cp",
latestUpdateTemp.getPath(),
"forkk.multimc.update.UpdateFile",
UpdateFile.getCurrentFilePath().toString(),
options);
System.out.println(UpdateFile.getCurrentFilePath().toString());
updateProcBuilder.start();
System.exit(0);
} catch (IOException e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(null,
"Couldn't launch updater: " +
e.toString(), "Updater Launch Failed",
JOptionPane.ERROR_MESSAGE);
}
// latestUpdateTemp.renameTo(
// UpdateFile.getCurrentFilePath().toFile());
//
// if (restartAfterUpdate)
// {
// ProcessBuilder restartProcBuilder = new ProcessBuilder(
// "java",
// "-jar",
// UpdateFile.getCurrentFilePath().toString());
// try
// {
// restartProcBuilder.start();
// } catch (IOException e)
// {
// e.printStackTrace();
// }
// }
}
System.exit(0);
}
});
} catch (Exception e)
{
e.printStackTrace();
}
}
});
}
/**
* The main logger for MultiMC
*/
public static final Logger mainLog = Logger.getLogger("forkk.MultiMC");
public static String getMainLogName()
{
return mainLog.getName();
}
/**
* Create the application.
*/
public SelectionWindow()
{
initialize();
}
/**
* Initialize the contents of the frame.
*/
private void initialize()
{
// Initialize Data
instList = new InstanceListModel();
// Initialize GUI
mainFrame = new JFrame();
mainFrame.addWindowListener(new WindowAdapter()
{
@Override
public void windowOpened(WindowEvent e)
{
File launcherFile = new File(AppSettings.getLauncherFilename());
if (!launcherFile.exists())
{
downloadLauncher();
}
if (AppSettings.getAutoUpdate())
{
checkUpdates();
}
}
});
mainFrame.setTitle(MainWindowTitle);
mainFrame.setBounds(100, 100, 620, 400);
mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
instListView = new JList();
instListView.addMouseListener(new MouseAdapter()
{
@Override
public void mousePressed(MouseEvent e)
{
if (e.getButton() == MouseEvent.BUTTON3)
{
instListView.setSelectedIndex(instListView.locationToIndex(e.getPoint()));
instPopupMenu.show(instListView, e.getPoint().x, e.getPoint().y);
}
if (e.getClickCount() >= 2 && e.getButton() == MouseEvent.BUTTON1)
{
actionPerformed(new ActionEvent(instListView,
ActionEvent.ACTION_PERFORMED, "launch"));
}
}
});
instListView.setLayoutOrientation(JList.HORIZONTAL_WRAP);
instListView.setVisibleRowCount(0);
instListView.setModel(instList);
instListView.setCellRenderer(new InstListRenderer());
instListView.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
new FileDrop(instListView, new Listener()
{
@Override
public void filesDropped(File[] files, Point point)
{
Instance inst = (Instance) instList.get(instListView.locationToIndex(point));
if (!inst.getInstMods().exists())
inst.getInstMods().mkdir();
for (File f : files)
{
try
{
forkk.multimc.util.FileUtils.recursiveCopy(f,
inst.getInstMods(), true);
inst.recursiveSetInstallTime(
new File(inst.getInstMods(), f.getName()),
System.currentTimeMillis());
} catch (IOException e)
{
e.printStackTrace();
}
}
rebuildJar(inst);
}
});
// instListView.addMouseListener(new MouseAdapter()
// {
// });
mainFrame.getContentPane().add(instListView, BorderLayout.CENTER);
instPopupMenu = new JPopupMenu();
instPopupMenu.setLabel("");
//addPopup(instListView, instPopupMenu);
mntmLaunch = new JMenuItem("Launch");
mntmLaunch.addActionListener(this);
instPopupMenu.add(mntmLaunch);
separator = new JSeparator();
instPopupMenu.add(separator);
mntmRename = new JMenuItem("Rename");
mntmRename.addActionListener(this);
instPopupMenu.add(mntmRename);
mntmChangeIcon = new JMenuItem("Change Icon");
mntmChangeIcon.addActionListener(this);
instPopupMenu.add(mntmChangeIcon);
mntmNotes = new JMenuItem("Notes");
mntmNotes.addActionListener(this);
instPopupMenu.add(mntmNotes);
mntmViewFolder = new JMenuItem("View Folder");
mntmViewFolder.addActionListener(this);
instPopupMenu.add(mntmViewFolder);
separator_1 = new JSeparator();
instPopupMenu.add(separator_1);
mntmEditMods = new JMenuItem("Edit Mods");
mntmEditMods.addActionListener(this);
instPopupMenu.add(mntmEditMods);
mntmRebuildMinecraftjar = new JMenuItem("Rebuild minecraft.jar");
mntmRebuildMinecraftjar.addActionListener(this);
instPopupMenu.add(mntmRebuildMinecraftjar);
separator_2 = new JSeparator();
instPopupMenu.add(separator_2);
mntmDeleteInstance = new JMenuItem("Delete Instance");
mntmDeleteInstance.addActionListener(this);
instPopupMenu.add(mntmDeleteInstance);
toolBar = new JToolBar();
toolBar.setFloatable(false);
mainFrame.getContentPane().add(toolBar, BorderLayout.NORTH);
btnNewInst = new JButton("");
toolBar.add(btnNewInst);
btnNewInst.setToolTipText("New Instance");
btnNewInst.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/NewInstIcon.png")));
btnNewInst.addActionListener(this);
btnViewFolder = new JButton("");
toolBar.add(btnViewFolder);
btnViewFolder.setToolTipText("View Folder");
btnViewFolder.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/ViewFolderIcon.png")));
btnViewFolder.addActionListener(this);
btnRefresh = new JButton("");
toolBar.add(btnRefresh);
btnRefresh.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/RefreshInstIcon.png")));
btnRefresh.setToolTipText("Refresh");
btnRefresh.addActionListener(this);
toolBar.add(new JToolBar.Separator());
btnSettings = new JButton("");
btnSettings.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/SettingsIcon.png")));
btnSettings.setToolTipText("Settings");
toolBar.add(btnSettings);
btnSettings.addActionListener(this);
btnCheckForUpdates = new JButton("");
btnCheckForUpdates.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/CheckUpdateIcon.png")));
btnCheckForUpdates.setToolTipText("Check for updates...");
toolBar.add(btnCheckForUpdates);
btnCheckForUpdates.addActionListener(this);
toolBar.add(Box.createHorizontalGlue());
btnReportBug = new JButton("");
btnReportBug.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(new URI(reportBugURL));
} catch (IOException e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(null, "MultiMC failed to " +
"open the webpage because it couldn't find a " +
"program to open " + reportBugURL, "Error",
JOptionPane.ERROR_MESSAGE);
} catch (URISyntaxException e)
{
e.printStackTrace();
JOptionPane.showMessageDialog(null, reportBugURL +
" is an invalid URL. Please report this bug!",
"Error", JOptionPane.ERROR_MESSAGE);
}
}
}
});
btnReportBug.setToolTipText("Report a Bug");
btnReportBug.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/ReportBugIcon.png")));
toolBar.add(btnReportBug);
btnHelp = new JButton("Help");
btnHelp.setEnabled(false);
btnHelp.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/HelpIcon.png")));
toolBar.add(btnHelp);
btnHelp.addActionListener(this);
btnAbout = new JButton("About");
btnAbout.setEnabled(false);
btnAbout.setIcon(new ImageIcon(SelectionWindow.class.getResource("/forkk/multimc/icons/AboutIcon.png")));
toolBar.add(btnAbout);
btnAbout.addActionListener(this);
statusBar = new JToolBar();
statusBar.setFloatable(false);
mainFrame.getContentPane().add(statusBar, BorderLayout.SOUTH);
lblTaskStatus = new JLabel("Task status...");
lblTaskStatus.setVisible(false);
statusBar.add(lblTaskStatus);
statusBar.add(Box.createHorizontalGlue());
taskProgressBar = new JProgressBar();
taskProgressBar.setAlignmentX(Component.RIGHT_ALIGNMENT);
taskProgressBar.setMinimumSize(new Dimension(100, 14));
taskProgressBar.setStringPainted(true);
statusBar.add(taskProgressBar);
mainFrame.setTitle(MainWindowTitle + " BETA " + Version.currentVersion);
loadInstances();
}
private static final File instanceDirectory = new File("instances");
private Task currentTask;
public void startTask(Task task)
{
if (isTaskRunning())
throw new InvalidParameterException("MultiMC can't multitask! " +
"(Background task can't start because one is already running)");
else
{
// Detach from the current task
if (currentTask != null)
{
currentTask.RemoveTaskListener(this);
}
// Attach the new one
currentTask = task;
currentTask.AddTaskListener(this);
// System.out.println("Started " + task.toString());
currentTask.start();
}
}
/**
* Checks for updates
*/
private void checkUpdates()
{
if (!isTaskRunning())
{
UpdateCheck checkTask = new UpdateCheck(UpdateCheck.VF_STANDARD);
checkTask.AddTaskListener(new TaskAdapter()
{
@Override
public void taskEnd(Task t)
{
UpdateCheck check = (UpdateCheck) t;
System.out.println("Latest version: " + check.getLatestVersion());
System.out.println(Version.currentVersion.compareTo(check.getLatestVersion()));
boolean updateAvailable = check.isUpdateAvailable();
if (updateAvailable)
{
downloadUpdates();
}
}
});
startTask(checkTask);
}
}
/**
* Downloads the latest version from the server to LatestVersion.jar
*/
private void downloadUpdates()
{
System.out.println("Downloading update...");
Downloader updateDownloader = new Downloader(
UpdateCheck.getLatestVersionURL(),
latestUpdateTemp);
updateDownloader.AddTaskListener(new TaskAdapter()
{
@Override
public void taskStart(Task t) { System.out.println("Download start"); }
@Override
public void taskEnd(Task t)
{
int reply = JOptionPane.showConfirmDialog(null,
"Updates have been downloaded and are ready to " +
"install.\nWould you like to install them?",
"Install Update?",
JOptionPane.YES_NO_OPTION);
if (reply == JOptionPane.YES_OPTION)
installUpdate(true);
else
latestUpdateTemp.delete();
}
});
startTask(updateDownloader);
}
/**
* Closes the program and replaces the current jar with LatestVersion.jar
* @param latestVersion the file that contains the latest version
* @param doItNow if true, MultiMC will update now and restart when finished.
* Otherwise, it will silently update when it closes
*/
private void installUpdate(boolean doItNow)
{
SelectionWindow.updateOnExit = true;
if (doItNow)
{
SelectionWindow.restartAfterUpdate = true;
mainFrame.dispatchEvent(new WindowEvent(mainFrame, WindowEvent.WINDOW_CLOSING));
System.exit(0);
//mainFrame.dispose();
}
}
private void downloadLauncher()
{
Downloader downloader = new Downloader(getLauncherDownloadURL(),
new File(AppSettings.getLauncherFilename()),
"Downloading Minecraft launcher...");
ForegroundTaskDialog dlDialog = new ForegroundTaskDialog(downloader);
dlDialog.setLocationRelativeTo(mainFrame);
startTask(downloader);
dlDialog.setVisible(true);
// try { downloader.wait(); } catch (InterruptedException e) {}
}
private void rebuildJar(Instance inst)
{
JarBuilder jarBuild = new JarBuilder(inst);
ForegroundTaskDialog buildDlg = new ForegroundTaskDialog(jarBuild);
buildDlg.setLocationRelativeTo(mainFrame);
startTask(jarBuild);
buildDlg.setVisible(true);
}
public boolean isTaskRunning()
{
return (currentTask != null && currentTask.isRunning());
}
/**
* Loads instances from the instance directory
*/
private void loadInstances()
{
try
{
instList.clear();
if (!instanceDirectory.exists())
instanceDirectory.mkdir();
for (File f : instanceDirectory.listFiles())
{
if (new File(f, Instance.InstanceDataFileName).exists())
{
try
{
instList.addElement(new Instance(f.toString()));
} catch (InstanceInitException e)
{
e.printStackTrace();
System.out.println("Failed to load instance from " + f.toString());
}
}
}
}
// catch (IOException e)
// {
// e.printStackTrace();
// }
finally{}
}
@Override
public void actionPerformed(ActionEvent event)
{
// Launch
if (event.getSource() == instListView || event.getSource() == mntmLaunch)
{
System.out.println("Launching " +
((Instance) instListView.getSelectedValue()).getName());
((Instance) instListView.getSelectedValue()).Launch();
}
/////////////////////////// MAIN TOOLBAR /////////////////////////////
// New instance
else if (event.getSource() == btnNewInst)
{
String instName = JOptionPane.showInputDialog(mainFrame,
"Type a name for your new instance:", "New Instance", JOptionPane.QUESTION_MESSAGE);
if (instName != null)
{
try
{
File instPath = new File(instanceDirectory,
instName.replaceAll("[^a-zA-Z0-9]", "_"));
Instance newInst = new Instance(instName, instPath.toString());
newInst.Save();
} catch (InstanceInitException e1)
{
e1.printStackTrace();
} catch (InstanceSaveException e1)
{
e1.printStackTrace();
}
loadInstances();
}
}
// View folder
else if (event.getSource() == btnViewFolder)
{
// FIXME View folder buttons don't work on Linux
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse(instanceDirectory.toURI());
} catch (IOException e)
{
e.printStackTrace();
}
}
}
// Refresh
else if (event.getSource() == btnRefresh)
{
loadInstances();
}
// Settings
else if (event.getSource() == btnSettings)
{
SettingsDialog sdlg = new SettingsDialog();
sdlg.setVisible(true);
}
// Update
else if (event.getSource() == btnCheckForUpdates)
{
checkUpdates();
}
// Help
else if (event.getSource() == btnHelp)
{
// TODO Help button
}
// About
else if (event.getSource() == btnAbout)
{
// TODO About button
}
////////////////////////// INSTANCE RIGHT CLICK MENU //////////////////
// Rename
else if (event.getSource() == mntmRename)
{
String newName = JOptionPane.showInputDialog(null,
"Enter a new name:", "Rename", JOptionPane.PLAIN_MESSAGE);
((Instance) instListView.getSelectedValue()).setName(newName);
loadInstances();
}
// Change icon
else if (event.getSource() == mntmChangeIcon)
{
ChangeIconDialog chd = new ChangeIconDialog(
(Instance) instListView.getSelectedValue());
chd.setVisible(true);
}
// Notes
else if (event.getSource() == mntmNotes)
{
EditNotesDialog end = new EditNotesDialog(
(Instance) instListView.getSelectedValue());
end.setVisible(true);
}
// View Folder
else if (event.getSource() == mntmViewFolder)
{
// FIXME View folder buttons don't work on Linux
if (Desktop.isDesktopSupported())
{
try
{
Desktop.getDesktop().browse((
(Instance) instListView.getSelectedValue()).getRootDir().toURI());
} catch (IOException e)
{
e.printStackTrace();
}
}
}
// Edit Mods
else if (event.getSource() == mntmEditMods)
{
Instance inst = (Instance) instListView.getSelectedValue();
EditModsDialog editDlg = new EditModsDialog(inst);
editDlg.setModal(true);
editDlg.setVisible(true);
rebuildJar(inst);
}
// Rebuild Jar
else if (event.getSource() == mntmRebuildMinecraftjar)
{
rebuildJar((Instance) instListView.getSelectedValue());
}
// Delete
else if (event.getSource() == mntmDeleteInstance)
{
String reply = JOptionPane.showInputDialog(null, "Type DELETE in " +
"all caps to delete this instance. THIS CANNOT BE UNDONE!",
"Are you sure?", JOptionPane.QUESTION_MESSAGE);
if (reply != null && reply.equals("DELETE"))
{
Instance inst = (Instance) instListView.getSelectedValue();
System.out.println("Deleting instance " + inst.getName());
instList.removeElement(inst);
inst.getRootDir().delete();
loadInstances();
}
}
}
@Override
public void taskProgressChange(Task t, int p)
{
if (p > 0)
taskProgressBar.setValue(p);
else
taskProgressBar.setIndeterminate(true);
}
@Override
public void taskStart(Task t)
{
lblTaskStatus.setVisible(isTaskRunning());
taskProgressBar.setVisible(isTaskRunning());
taskProgressBar.setIndeterminate(t.isProgressIndeterminate());
taskProgressBar.setStringPainted(!t.isProgressIndeterminate());
}
@Override
public void taskEnd(Task t)
{
lblTaskStatus.setVisible(isTaskRunning());
taskProgressBar.setVisible(isTaskRunning());
}
@Override
public void taskStatusChange(Task t, String status)
{
System.out.println("Status: " + status);
lblTaskStatus.setText(status);
}
@Override
public void taskErrorMessage(Task t, String message)
{
JOptionPane.showMessageDialog(null, message, "Error",
JOptionPane.ERROR_MESSAGE);
}
InstanceListModel instList;
DropTarget instListDT;
private JButton btnNewInst;
private JButton btnViewFolder;
private JButton btnRefresh;
private JButton btnSettings;
private JButton btnCheckForUpdates;
private JButton btnHelp;
private JButton btnAbout;
private JToolBar toolBar;
private JToolBar statusBar;
private JLabel lblTaskStatus;
private JProgressBar taskProgressBar;
private JList instListView;
private JPopupMenu instPopupMenu;
private JMenuItem mntmRename;
private JSeparator separator;
private JMenuItem mntmChangeIcon;
private JMenuItem mntmNotes;
private JSeparator separator_1;
private JMenuItem mntmEditMods;
private JMenuItem mntmRebuildMinecraftjar;
private JMenuItem mntmViewFolder;
private JSeparator separator_2;
private JMenuItem mntmDeleteInstance;
private JMenuItem mntmLaunch;
private JButton btnReportBug;
}