import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.prefs.Preferences;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SpringLayout;
import javax.swing.UIManager;
import javax.swing.filechooser.FileFilter;
public class Main {
public static final int APPLICATION_VERSION = 1;
protected static final strings S = new strings();
private static final String DS = "/"; // Directory separator
protected static Integer JSKY_DEFAULT_PADDING = 7;
protected static Integer PREFFERED_WIDTH = 485;
protected static Integer PREFFERED_HEIGHT = 500;
protected static File wow_path;
protected static File wtf_path; // determined later by "wow_path", if known.
protected static File accs_path;// determined later by "wow_path", if known.
protected static final String AddonLuaFileName = "BankerkaBags.lua";
protected static final Integer TARGET_BANKERKABAGS_LUA_VERSION = 1;
protected static final String BB_DEFAULT_ENCODING = "UTF-8";
private static final String WEB_ACTION_UPDATE = "update";
// protected static String TARGET_HOST_URL =
// "http://myjursky.php5.sk/bankerkabags/";
protected static String TARGET_HOST_URL = "http://localhost/bankerkabags/";
protected static Preferences cfg = Preferences
.userNodeForPackage(Main.class);
protected static JFrame mainWindowFrm = new JFrame(S.APPNAME);
protected static ArrayList<File> charsLuasToSync = new ArrayList<>();
protected static ArrayList<WowCharacter> wowCharacters = new ArrayList<>();
public static void main(String[] args) {
mainWindowFrm.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setDefaultFonts();
updatePanels();
}
private static void setDefaultFonts() {
Font appDefaultFont = new Font("Verdana", Font.PLAIN, 11);
UIManager.put("Button.font", appDefaultFont);
UIManager.put("ToggleButton.font", appDefaultFont);
UIManager.put("RadioButton.font", appDefaultFont);
UIManager.put("CheckBox.font", appDefaultFont);
UIManager.put("ColorChooser.font", appDefaultFont);
UIManager.put("ComboBox.font", appDefaultFont);
UIManager.put("Label.font", appDefaultFont);
UIManager.put("List.font", appDefaultFont);
UIManager.put("MenuBar.font", appDefaultFont);
UIManager.put("MenuItem.font", appDefaultFont);
UIManager.put("RadioButtonMenuItem.font", appDefaultFont);
UIManager.put("CheckBoxMenuItem.font", appDefaultFont);
UIManager.put("Menu.font", appDefaultFont);
UIManager.put("PopupMenu.font", appDefaultFont);
UIManager.put("OptionPane.font", appDefaultFont);
UIManager.put("Panel.font", appDefaultFont);
UIManager.put("ProgressBar.font", appDefaultFont);
UIManager.put("ScrollPane.font", appDefaultFont);
UIManager.put("Viewport.font", appDefaultFont);
UIManager.put("TabbedPane.font", appDefaultFont);
UIManager.put("Table.font", appDefaultFont);
UIManager.put("TableHeader.font", appDefaultFont);
UIManager.put("TextField.font", appDefaultFont);
UIManager.put("PasswordField.font", appDefaultFont);
UIManager.put("TextArea.font", appDefaultFont);
UIManager.put("TextPane.font", appDefaultFont);
UIManager.put("EditorPane.font", appDefaultFont);
UIManager
.put("TitledBorder.font", appDefaultFont.deriveFont(Font.BOLD));
UIManager.put("ToolBar.font", appDefaultFont);
UIManager.put("ToolTip.font", appDefaultFont);
UIManager.put("Tree.font", appDefaultFont);
}
static JPanel createPanel_wowExeLocChoose() {
JPanel wowChoosePnl = new JPanel();
wowChoosePnl.setBorder(BorderFactory
.createTitledBorder(S.WOW_EXE_FILE_LOCATION_SELECTION));
// World of WarCraft installment location choose panel
final JTextField wowExeLocTxtFld = new JTextField();
if (cfg != null && cfg.get("WOW_EXE_LOCATION", null) != null) {
// If we know WoW directory, set-up variables for WTF subdirectories
// (accs, chars, ..)
wow_path = new File(cfg.get("WOW_EXE_LOCATION", ""));
// TODO: sub-directory paths
wtf_path = new File(wow_path.getAbsolutePath() + DS + "WTF");
accs_path = new File(wtf_path.getAbsolutePath() + DS + "Account");
wowExeLocTxtFld.setText(wow_path.getAbsolutePath());
}
wowExeLocTxtFld.setColumns(30);
wowExeLocTxtFld.setEnabled(false);
wowChoosePnl.add(wowExeLocTxtFld);
JButton wowExeLocBttn = new JButton(S.BROWSE_FILE);
wowExeLocBttn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JFileChooser wowExeLocFlChsr = new JFileChooser();
if (wow_path != null) {
File wowExeFile = (wow_path);
if (wowExeFile.exists())
wowExeLocFlChsr.setCurrentDirectory(wowExeFile);
}
wowExeLocFlChsr
.setDialogTitle(S.WOW_EXE_FILE_LOCATION_SELECTION);
wowExeLocFlChsr.setFileFilter(new FileFilter() {
@Override
public String getDescription() {
return "wow.exe, launcher.exe";
}
@Override
public boolean accept(File arg0) {
return arg0.isDirectory()
|| arg0.getName().equalsIgnoreCase("wow.exe")
|| arg0.getName().toLowerCase()
.equalsIgnoreCase("launcher.exe");
}
});
int result = wowExeLocFlChsr.showOpenDialog(null);
switch (result) {
case JFileChooser.APPROVE_OPTION: {
String n = wowExeLocFlChsr.getSelectedFile().getName();
if (n.length() == 0
|| (!n.equalsIgnoreCase("wow.exe") && !n
.equalsIgnoreCase("laucher.exe"))) {
// TODO: remove "wrong file" popup? I think it should be
// there, but what if someone is starting World of
// Warcraft somehow else than by default way?
// JOptionPane.showMessageDialog(new JFrame(),
// S.WRONG_FILE_SELECTED_FULL + ".");
if (1 == JOptionPane.showConfirmDialog(
mainWindowFrm,
String.format(
"<html><p style='width: 400px;'>%s.</p></html>",
S.WRONG_FILE_SELECTED_FULL
+ ". "
+ S.HOWEVER_WOULD_YOU_STILL_LIKE_TO_USE_THIS_FILE
+ " "
+ S.PROGRAM_MAY_NOT_WORK_PROPERLY),
S.WRONG_FILE_SELECTED,
JOptionPane.YES_NO_OPTION))
break;
}
String wowExeFullLoc = wowExeLocFlChsr.getSelectedFile()
.getAbsolutePath();
wowExeFullLoc = wowExeFullLoc.substring(0,
wowExeFullLoc.lastIndexOf(File.separator));
wowExeLocTxtFld.setText(wowExeFullLoc);
cfg.put("WOW_EXE_LOCATION", wowExeFullLoc);
updatePanels();
break;
}
}
}
});
wowChoosePnl.add(wowExeLocBttn);
return wowChoosePnl;
}
static JPanel createPanel_wowCharactersSelection() {
JPanel wowCharsSelPnl = new JPanel(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
wowCharsSelPnl.setBorder(BorderFactory
.createTitledBorder(S.WOW_CHARACTERS_SELECTION));
if (wow_path != null) {
JPanel accsChckBoxesPnl = new JPanel(new GridBagLayout());
GridBagConstraints accsChckBoxesPnl_c = new GridBagConstraints();
accsChckBoxesPnl_c.fill = GridBagConstraints.HORIZONTAL;
accsChckBoxesPnl_c.gridx = 0;
accsChckBoxesPnl_c.gridy = 0;
// Look for available accounts and its saved variables
try {
File[] accountsDirs = (accs_path).listFiles();
String charsSelTxt = S.SELECT_CHARS_TO_SYNC;
if (accountsDirs == null) {
charsSelTxt = S.NO_CHARACTERS_AVAILABLE
+ ". "
+ S.PLEASE_MAKE_SURE_YOU_SELECTED_PROPER_WOW_DIR
+ ". "
+ String.format("%s. %s",
S.MAKE_SURE_YOU_HAVE_ACCESS_TO_DIR,
S.ADMINISTRATOR_PRIVILEGIES_MAY_BE_REQUIRED);
}
wowCharsSelPnl
.add(new JLabel(
String.format(
"<html><div style='width: 337px; padding: 5px; text-align: justify;'>%s.</div></html>",
charsSelTxt)), c);
if (accountsDirs == null)
return wowCharsSelPnl;
for (final File accDir : accountsDirs) {
// dout(accDir.getName(), true);
final JPanel accCharsPnl = new JPanel();
accCharsPnl.setLayout(new GridBagLayout());
GridBagConstraints accCharsPnl_c = new GridBagConstraints();
accCharsPnl_c.fill = GridBagConstraints.HORIZONTAL;
accCharsPnl_c.gridy = 0;
accCharsPnl.setBorder(BorderFactory
.createTitledBorder(S.ACCOUNT + " "
+ accDir.getName()));
final JCheckBox accEnabledChkbox = new JCheckBox(S.SYNC
+ " " + S.FOR.toLowerCase() + " "
+ accDir.getName() + " " + S.ENABLED.toLowerCase());
accEnabledChkbox.setSelected(cfg.getBoolean(
"AccountAllowance_" + accDir.getName(), false));
accEnabledChkbox.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
if (e.getStateChange() == ItemEvent.SELECTED
|| e.getStateChange() == ItemEvent.DESELECTED) {
boolean chkbox_enabled = e.getStateChange() == ItemEvent.SELECTED;
cfg.putBoolean(
"AccountAllowance_" + accDir.getName(),
chkbox_enabled);
// Disable all JCheckBox subcomponents
// (account'� characters allowance).
Component[] cmps = accCharsPnl.getComponents();
for (Component cmp : cmps) {
// We want only JCheckBox(es).
if (!(cmp instanceof JCheckBox)
|| cmp == accEnabledChkbox)
continue;
JCheckBox jchkbox = ((JCheckBox) cmp);
jchkbox.setEnabled(chkbox_enabled);
for (ItemListener il : ((JCheckBox) cmp)
.getItemListeners()) {
int oldState = jchkbox.isSelected() ? ItemEvent.SELECTED
: ItemEvent.DESELECTED;
ItemEvent fakeItemEvent = new ItemEvent(
accEnabledChkbox,
ItemEvent.ITEM_STATE_CHANGED,
jchkbox,
!chkbox_enabled ? ItemEvent.DESELECTED
: oldState);
il.itemStateChanged(fakeItemEvent);
}
}
}
}
});
accCharsPnl.add(accEnabledChkbox, accCharsPnl_c);
++accCharsPnl_c.gridy;
// Look for Account's servers
File[] accServersDirs = accDir
.listFiles(new java.io.FileFilter() {
@Override
public boolean accept(File arg0) {
return arg0.isDirectory()
&& !arg0.getName().equals(
"SavedVariables");
}
});
for (final File serverDir : accServersDirs) {
File[] serverCharsDirs = serverDir
.listFiles(new java.io.FileFilter() {
@Override
public boolean accept(File arg0) {
// FIXME: make straight checking for
// BankerkasBags.lua file?
File charAddonLuaFile = new File(arg0
.getAbsolutePath()
+ DS
+ "SavedVariables"
+ DS
+ AddonLuaFileName);
return arg0.isDirectory()
&& charAddonLuaFile.exists();
}
});
for (final File charDir : serverCharsDirs) {
// Add current character to list for sync (create
// new character object and put it to the list among
// the others).
final WowCharacter wowChar = new WowCharacter(
charDir.getName(), new File(
charDir.getAbsoluteFile() + DS
+ "SavedVariables" + DS
+ AddonLuaFileName));
// Populate Char's inventory.
// wowChar.populateBagsItems(); // TODO: not needed
// now..
JCheckBox charAllowanceChkbox = new JCheckBox(
charDir.getName() + " ("
+ serverDir.getName() + ")");
charAllowanceChkbox.setEnabled(accEnabledChkbox
.isSelected());
boolean isWowCharChkboxSelected = cfg.getBoolean(
"CharacterAllowance_" + serverDir.getName()
+ "_" + charDir.getName(), false);
charAllowanceChkbox
.setSelected(isWowCharChkboxSelected);
if (accEnabledChkbox.isEnabled()
&& accEnabledChkbox.isSelected()
&& isWowCharChkboxSelected)
wowCharacters.add(wowChar);
charAllowanceChkbox
.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(
ItemEvent arg0) {
boolean newStateIsSelected = arg0
.getStateChange() == ItemEvent.SELECTED;
cfg.putBoolean(
"CharacterAllowance_"
+ serverDir
.getName()
+ "_"
+ charDir.getName(),
newStateIsSelected);
if (newStateIsSelected)
wowCharacters.add(wowChar);
else
wowCharacters.remove(wowChar);
}
});
++accCharsPnl_c.gridy;
accCharsPnl_c.insets = new Insets(0, 20, 0, 0);
accCharsPnl.add(charAllowanceChkbox, accCharsPnl_c);
}
}
if ((accCharsPnl.getComponentCount() - 1) == 0) {
++accCharsPnl_c.gridy;
accCharsPnl_c.fill = GridBagConstraints.NONE; // Will
// center
// text.
accCharsPnl_c.insets = new Insets(0, 5, 3, 5);
accCharsPnl.add(new JLabel(
S.NO_CHARACTERS_MET_REQUIREMENTS),
accCharsPnl_c);
accEnabledChkbox.setEnabled(false);
accEnabledChkbox.setToolTipText(String.format(
"<html><p>%s.%s%s.</p></html>",
S.NO_CHARACTERS_MET_REQUIREMENTS_FOR_SYNC,
S.HTML_NL, S.THIS_ACCOUNT_WOULDNT_BE_SYNCED));
accCharsPnl_c.insets = null;
accCharsPnl.setEnabled(false);
}
accsChckBoxesPnl.add(accCharsPnl, accsChckBoxesPnl_c);
if (++accsChckBoxesPnl_c.gridx == 2) {
accsChckBoxesPnl_c.gridx = 0;
++accsChckBoxesPnl_c.gridy;
}
}
} catch (SecurityException e) {
JOptionPane.showMessageDialog(null,
S.EXCEPTION_SecurityException + ".");
return accsChckBoxesPnl;
}
c.gridy = 1;
wowCharsSelPnl.add(accsChckBoxesPnl, c);
// Buttons panel for Accounts selection (e.g. Refresh available
// accounts, start Sync, etc..)
JPanel wowCharsActionsPnl = new JPanel(new FlowLayout(
FlowLayout.CENTER, 10, 2));
// Refresh available accounts of the chosen World of Warcraft
// directory
JButton refreshAvailCharsBttn = new JButton(
S.REFRESH_AVAIL_ACCOUNTS);
refreshAvailCharsBttn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
updatePanels();
}
});
wowCharsActionsPnl.add(refreshAvailCharsBttn);
// Start sync
final JButton startSyncBttn = new JButton(S.SYNC_NOW + "!");
startSyncBttn.setFont(((Font) UIManager.getDefaults().get(
"Button.font")).deriveFont(Font.BOLD));
startSyncBttn.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
// Disable button until synchronization is completed.
startSyncBttn.setEnabled(false);
if (!startSync())
JOptionPane.showMessageDialog(
null,
String.format(
"<html>%s</html>",
S.SYNC_UNSUCCESSFUL
.replace("unsuccessful",
"<strong style='color: red;'>unsuccessful!</strong>")
+ S.HTML_NL
+ S.CHECK_INTERNET_CONNECTION
+ "."
+ S.HTML_NL
+ S.CHECK_IF_CHARS_ARE_SELECTED
+ "."));
startSyncBttn.setEnabled(true);
}
});
wowCharsActionsPnl.add(startSyncBttn);
++c.gridy;
c.insets = new Insets(JSKY_DEFAULT_PADDING, JSKY_DEFAULT_PADDING,
JSKY_DEFAULT_PADDING, JSKY_DEFAULT_PADDING);
wowCharsSelPnl.add(wowCharsActionsPnl, c);
} else
wowCharsSelPnl.setEnabled(false);
return wowCharsSelPnl;
}
protected static void updateButtons() {
// If no one character was selected for synchronization, disable
// "Sync Now!" button.
// TODO: this method maybe will not be needed at all..
}
public static void buildWowCharacters() {
//
}
/**
* Method which actually do synchronization. This function is invoked upon
* "Start Sync!" button click.
*
* @return true on successful synchronization, false otherwise.
* */
static boolean startSync() {
// If characters array is empty, it is not necessary to sync at all.
if (wowCharacters.size() == 0)
return false;
for (WowCharacter wowChar : wowCharacters) {
try {
// Update bags information URI query creation.
String urlParameters = "javaBankerkaBagsVersion="
+ URLEncoder.encode(
String.valueOf(APPLICATION_VERSION), "UTF-8")
+ "&action="
+ URLEncoder.encode(WEB_ACTION_UPDATE, "UTF-8")
+ "&jsondata="
+ URLEncoder.encode(
wowChar.getPlainBankerkaJSONString(), "UTF-8");
dout(TARGET_HOST_URL + "?" + urlParameters);
// HTTP POST request
String response = excutePost(TARGET_HOST_URL, urlParameters);
// dout(response);
JList<String> respList = new JList<String>(
new String[] { "<html>"
+ response.replaceAll("\n", "<br>") + "</html>" });
JScrollPane respPane = new JScrollPane(respList);
respPane.setPreferredSize(new Dimension(600, 400));
JOptionPane.showMessageDialog(null, respPane,
"Server response", JOptionPane.PLAIN_MESSAGE);
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* Refreshes panels of World of WarCraft characters selection (called upon
* program start and wow/launcher.exe file selection) TODO: Rewrite whole
* method to be able refresh only one element/component at a time.
*/
static void updatePanels() {
mainWindowFrm.getContentPane().removeAll();
SpringLayout layout = new SpringLayout();
mainWindowFrm.setLayout(layout);
// create new panels
Component c1 = mainWindowFrm.getContentPane().add(
createPanel_wowExeLocChoose());
Component c2 = mainWindowFrm.getContentPane().add(
createPanel_wowCharactersSelection());
// Main window padding (constraints instead of one additional JPanel
// padding..)
layout.putConstraint(SpringLayout.NORTH, c1, JSKY_DEFAULT_PADDING,
SpringLayout.NORTH, mainWindowFrm);
layout.putConstraint(SpringLayout.WEST, c1, JSKY_DEFAULT_PADDING,
SpringLayout.WEST, mainWindowFrm);
layout.putConstraint(SpringLayout.NORTH, c2, 0, SpringLayout.SOUTH, c1);
layout.putConstraint(SpringLayout.EAST, c2, 0, SpringLayout.EAST, c1);
layout.putConstraint(SpringLayout.WEST, c2, 0, SpringLayout.WEST, c1);
// dout("Components count: " +
// mainWindowFrm.getContentPane().getComponentCount(), true);
mainWindowFrm.revalidate();
mainWindowFrm.repaint();
mainWindowFrm.setMinimumSize(new Dimension(PREFFERED_WIDTH,
PREFFERED_HEIGHT));
mainWindowFrm.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
mainWindowFrm.setSize(new Dimension(PREFFERED_WIDTH,
mainWindowFrm.getHeight()));
super.componentResized(e);
}
});
mainWindowFrm.setVisible(true);
}
static void dout(String txt, boolean nl) {
if (nl == true)
System.out.println(txt);
else
System.out.printf("%s", txt);
}
static void dout(Integer number, boolean nl) {
dout(number.toString(), nl);
}
static void dout(Object obj, boolean nl) {
dout(obj.toString(), nl);
}
static void dout(Object obj) {
dout(obj != null ? obj.toString() : "dout: " + null, true);
}
public static String excutePost(String targetURL, String urlParameters) {
URL url;
HttpURLConnection connection = null;
try {
// Create connection
url = new URL(targetURL);
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded");
connection.setRequestProperty("Content-Length",
"" + Integer.toString(urlParameters.getBytes().length));
connection.setRequestProperty("Content-Language", "en-US");
connection.setUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(true);
// Send request
DataOutputStream wr = new DataOutputStream(
connection.getOutputStream());
wr.writeBytes(urlParameters);
wr.flush();
wr.close();
// Get Response
InputStream is = connection.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
String line;
StringBuffer response = new StringBuffer();
while ((line = rd.readLine()) != null) {
response.append(line);
response.append('\r');
}
rd.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (connection != null) {
connection.disconnect();
}
}
}
}