/*
* This file is part of The Technic Launcher Version 3.
* Copyright ©2015 Syndicate, LLC
*
* The Technic Launcher 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.
*
* The Technic Launcher 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 the Technic Launcher. If not, see <http://www.gnu.org/licenses/>.
*/
package net.technicpack.launcher.ui;
import net.technicpack.launcher.LauncherMain;
import net.technicpack.ui.controls.list.popupformatters.RoundedBorderFormatter;
import net.technicpack.ui.controls.lang.LanguageCellRenderer;
import net.technicpack.ui.controls.lang.LanguageCellUI;
import net.technicpack.ui.controls.list.SimpleButtonComboUI;
import net.technicpack.ui.lang.IRelocalizableResource;
import net.technicpack.ui.lang.ResourceLoader;
import net.technicpack.launcher.settings.TechnicSettings;
import net.technicpack.ui.controls.DraggableFrame;
import net.technicpack.ui.controls.RoundedButton;
import net.technicpack.ui.controls.borders.RoundBorder;
import net.technicpack.ui.controls.login.*;
import net.technicpack.ui.listitems.LanguageItem;
import net.technicpack.launchercore.auth.IAuthListener;
import net.technicpack.launchercore.auth.IUserType;
import net.technicpack.minecraftcore.mojang.auth.MojangUser;
import net.technicpack.launchercore.auth.UserModel;
import net.technicpack.launchercore.exception.AuthenticationNetworkFailureException;
import net.technicpack.launchercore.image.ImageRepository;
import net.technicpack.utilslib.DesktopUtils;
import net.technicpack.utilslib.Utils;
import javax.swing.*;
import javax.swing.border.LineBorder;
import javax.swing.plaf.metal.MetalComboBoxUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Collection;
import java.util.Comparator;
import java.util.Locale;
import java.util.logging.Level;
public class LoginFrame extends DraggableFrame implements IRelocalizableResource, KeyListener, IAuthListener<MojangUser> {
private ResourceLoader resources;
private ImageRepository<IUserType> skinRepository;
private UserModel<MojangUser> userModel;
private TechnicSettings settings;
private JTextField name;
private JComboBox nameSelect;
private JCheckBox rememberAccount;
private JPasswordField password;
private JComboBox languages;
private static final int FRAME_WIDTH = 347;
private static final int FRAME_HEIGHT = 409;
public LoginFrame(ResourceLoader resources, TechnicSettings settings, UserModel userModel, ImageRepository<IUserType> skinRepository) {
this.skinRepository = skinRepository;
this.userModel = userModel;
this.settings = settings;
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
getContentPane().setBackground(LauncherFrame.COLOR_CENTRAL_BACK_OPAQUE);
this.setFocusTraversalPolicy(new SortingFocusTraversalPolicy(new Comparator<Component>() {
@Override
public int compare(Component o1, Component o2) {
//This long stupid stack of else/ifs enforces a tab order of
//Username -> Password -> Remember me -> any buttons -> everything else who cares
if (o1 == name || o1 == nameSelect)
return -1;
else if (o2 == name || o2 == nameSelect)
return 1;
else if (o1 == password)
return -1;
else if (o2 == password)
return 1;
else if (o1 == rememberAccount)
return -1;
else if (o2 == rememberAccount)
return 1;
else if (o1 instanceof AbstractButton)
return -1;
else if (o2 instanceof AbstractButton)
return 1;
else
return 0;
}
}));
//Handles rebuilding the frame, so use it to build the frame in the first place
relocalize(resources);
setLocationRelativeTo(null);
}
protected void closeButtonClicked() {
System.exit(0);
}
protected void changeUser() {
if (nameSelect.getSelectedItem() == null || nameSelect.getSelectedItem().equals("")) {
clearCurrentUser();
} else if (nameSelect.getSelectedItem() instanceof MojangUser) {
setCurrentUser((MojangUser)nameSelect.getSelectedItem());
}
}
protected void toggleRemember() {
if (!rememberAccount.isSelected() && nameSelect.isVisible() && nameSelect.getSelectedItem() instanceof MojangUser) {
forgetUser((MojangUser)nameSelect.getSelectedItem());
}
}
protected void visitTerms() {
DesktopUtils.browseUrl("http://www.technicpack.net/terms");
}
protected void visitPrivacy() {
DesktopUtils.browseUrl("http://www.technicpack.net/privacy");
}
@Override
public void keyTyped(KeyEvent e) {
}
@Override
public void keyPressed(KeyEvent e) {
}
@Override
public void keyReleased(KeyEvent e) {
if (e.getSource() == rememberAccount && e.getKeyCode() == KeyEvent.VK_ENTER) {
attemptLogin();
}
}
protected void refreshUsers() {
Collection<MojangUser> mojangUserAccounts = userModel.getUsers();
MojangUser lastMojangUser = userModel.getLastUser();
if (mojangUserAccounts.size() == 0) {
name.setVisible(true);
nameSelect.setVisible(false);
clearCurrentUser();
} else {
name.setVisible(false);
nameSelect.setVisible(true);
nameSelect.removeAllItems();
for (MojangUser account : mojangUserAccounts) {
nameSelect.addItem(account);
}
nameSelect.addItem(null);
if (lastMojangUser == null)
lastMojangUser = mojangUserAccounts.iterator().next();
setCurrentUser(lastMojangUser);
}
}
protected void attemptLogin() {
if (nameSelect.isVisible()) {
Object selected = nameSelect.getSelectedItem();
if (selected instanceof MojangUser) {
verifyExistingLogin((MojangUser) selected);
} else {
String username = selected.toString();
MojangUser mojangUser = userModel.getUser(username);
if (mojangUser == null)
attemptNewLogin(username);
else {
setCurrentUser(mojangUser);
verifyExistingLogin(mojangUser);
}
}
} else {
attemptNewLogin(name.getText());
}
}
private void verifyExistingLogin(MojangUser mojangUser) {
MojangUser loginMojangUser = mojangUser;
boolean rejected = false;
try {
UserModel.AuthError error = userModel.attemptUserRefresh(mojangUser);
if (error != null) {
JOptionPane.showMessageDialog(this, error.getErrorDescription(), error.getError(), JOptionPane.ERROR_MESSAGE);
loginMojangUser = null;
rejected = true;
}
} catch (AuthenticationNetworkFailureException ex) {
Utils.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
//Couldn't reach auth server- if we're running silently (we just started up and have a user session ready to roll)
//Go ahead and just play offline automatically, like the minecraft client does. If we're running loud (user
//is actually at the login UI clicking the login button), give them a choice.
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(this,
"The auth servers at Minecraft.net are inaccessible. Would you like to play offline?",
"Offline Play", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE)) {
//This is the last time we'll have access to the user's real username, so we should set the last-used
//username now
userModel.setLastUser(mojangUser);
//Create offline user
loginMojangUser = new MojangUser(mojangUser.getDisplayName());
} else {
//Use clicked 'no', so just pull the ripcord and get back to the UI
loginMojangUser = null;
}
}
if (loginMojangUser == null) {
//If we actually failed to validate, we should remove the user from the list of saved users
//and refresh the user list
if (rejected) {
userModel.removeUser(mojangUser);
refreshUsers();
setCurrentUser(mojangUser.getUsername());
}
}
}
private void attemptNewLogin(String name) {
UserModel.AuthError error = userModel.attemptInitialLogin(name, new String(this.password.getPassword()));
if (error != null) {
JOptionPane.showMessageDialog(this, error.getErrorDescription(), error.getError(), JOptionPane.ERROR_MESSAGE);
} else if (rememberAccount.isSelected()) {
userModel.addUser(userModel.getCurrentUser());
}
}
protected void clearCurrentUser() {
password.setText("");
password.setEditable(true);
password.setForeground(LauncherFrame.COLOR_BUTTON_BLUE);
password.setBorder(new RoundBorder(LauncherFrame.COLOR_BUTTON_BLUE, 1, 10));
rememberAccount.setSelected(false);
name.setText("");
nameSelect.setSelectedItem("");
}
protected void setCurrentUser(MojangUser mojangUser) {
if (mojangUser == null) {
clearCurrentUser();
return;
}
password.setText("PASSWORD");
password.setEditable(false);
password.setForeground(LauncherFrame.COLOR_SCROLL_THUMB);
password.setBorder(new RoundBorder(LauncherFrame.COLOR_SCROLL_THUMB, 1, 10));
rememberAccount.setSelected(true);
nameSelect.setSelectedItem(mojangUser);
}
protected void setCurrentUser(String user) {
if (this.name.isVisible())
this.name.setText(user);
else
this.nameSelect.setSelectedItem(user);
password.setText("");
password.setEditable(true);
password.setForeground(LauncherFrame.COLOR_BUTTON_BLUE);
password.setBorder(new RoundBorder(LauncherFrame.COLOR_BUTTON_BLUE, 1, 10));
rememberAccount.setSelected(true);
}
protected void forgetUser(MojangUser mojangUser) {
userModel.removeUser(mojangUser);
refreshUsers();
}
protected void languageChanged() {
String langCode = ((LanguageItem)languages.getSelectedItem()).getLangCode();
settings.setLanguageCode(langCode);
resources.setLocale(langCode);
}
/**
* Generate & setup UI components for the frame
*/
private void initComponents() {
setLayout(new GridBagLayout());
//Close button
JButton closeButton = new JButton();
closeButton.setContentAreaFilled(false);
closeButton.setBorder(BorderFactory.createEmptyBorder());
closeButton.setIcon(resources.getIcon("close.png"));
closeButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
closeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
closeButtonClicked();
}
});
closeButton.setFocusable(false);
add(closeButton, new GridBagConstraints(2,0,1,1, 0.0, 0.0, GridBagConstraints.NORTHEAST, GridBagConstraints.NONE, new Insets(7,0,0,7),0,0));
//Logo at the top
JLabel platformImage = new JLabel();
platformImage.setIcon(resources.getIcon("platform_logo.png"));
add(platformImage, new GridBagConstraints(0,0,3,1,0.0,0.0,GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(30,0,0,0),0,0));
JLabel instructionText = new JLabel("<html><body align=\"center\">"+ resources.getString("login.instructions") +"</body></html>", JLabel.CENTER);
instructionText.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
instructionText.setForeground(LauncherFrame.COLOR_WHITE_TEXT);
add(instructionText, new GridBagConstraints(0, 1, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(9, 3, 0, 3), 0, 0));
JLabel userLabel = new JLabel(resources.getString("login.username"));
userLabel.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
userLabel.setForeground(LauncherFrame.COLOR_WHITE_TEXT);
add(userLabel, new GridBagConstraints(0, 2, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(10,20,0,20), 0,0));
// Setup username box
nameSelect = new JComboBox();
if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).contains("mac")) {
nameSelect.setUI(new MetalComboBoxUI());
}
nameSelect.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
nameSelect.setEditable(true);
nameSelect.setBorder(new RoundBorder(LauncherFrame.COLOR_BUTTON_BLUE, 1, 10));
nameSelect.setForeground(LauncherFrame.COLOR_BUTTON_BLUE);
nameSelect.setBackground(LauncherFrame.COLOR_FORMELEMENT_INTERNAL);
nameSelect.setVisible(false);
UserCellRenderer userRenderer= new UserCellRenderer(resources, this.skinRepository);
userRenderer.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
userRenderer.setSelectedBackgroundColor(LauncherFrame.COLOR_FORMELEMENT_INTERNAL);
userRenderer.setSelectedForegroundColor(LauncherFrame.COLOR_BUTTON_BLUE);
userRenderer.setUnselectedBackgroundColor(LauncherFrame.COLOR_CENTRAL_BACK_OPAQUE);
userRenderer.setUnselectedForegroundColor(LauncherFrame.COLOR_BUTTON_BLUE);
nameSelect.setRenderer(userRenderer);
UserCellEditor userEditor = new UserCellEditor(resources.getFont(ResourceLoader.FONT_OPENSANS, 16), this.skinRepository, LauncherFrame.COLOR_BUTTON_BLUE);
nameSelect.setEditor(userEditor);
userEditor.addKeyListener(this);
nameSelect.setUI(new SimpleButtonComboUI(new RoundedBorderFormatter(new RoundBorder(LauncherFrame.COLOR_BUTTON_BLUE, 1, 0)), resources, LauncherFrame.COLOR_SCROLL_TRACK, LauncherFrame.COLOR_SCROLL_THUMB));
nameSelect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
changeUser();
}
});
add(nameSelect, new GridBagConstraints(0, 3, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(3, 20, 0, 20), 4, 4));
name = new JTextField();
name.setBorder(new RoundBorder(LauncherFrame.COLOR_BUTTON_BLUE, 1, 10));
name.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
name.setBackground(LauncherFrame.COLOR_FORMELEMENT_INTERNAL);
name.setForeground(LauncherFrame.COLOR_BUTTON_BLUE);
name.setCaretColor(LauncherFrame.COLOR_BUTTON_BLUE);
name.addKeyListener(this);
add(name, new GridBagConstraints(0, 3, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(3,20,0,20),4,17));
JLabel passLabel = new JLabel(resources.getString("login.password"));
passLabel.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
passLabel.setForeground(LauncherFrame.COLOR_WHITE_TEXT);
add(passLabel, new GridBagConstraints(0, 4, 3, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(12,20,0,20),0,0));
// Setup password box
password = new JPasswordField();
password.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
password.setBorder(new RoundBorder(LauncherFrame.COLOR_BUTTON_BLUE, 1, 10));
password.setForeground(LauncherFrame.COLOR_BUTTON_BLUE);
password.setBackground(LauncherFrame.COLOR_FORMELEMENT_INTERNAL);
password.addKeyListener(this);
password.setEchoChar('*');
password.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
attemptLogin();
}
});
password.setCaretColor(LauncherFrame.COLOR_BUTTON_BLUE);
add(password, new GridBagConstraints(0, 5, 3, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(3, 20, 0, 20), 4, 17));
// "Remember this account"
Font rememberFont = resources.getFont(ResourceLoader.FONT_OPENSANS, 14);
rememberAccount = new JCheckBox("<html><body style=\"color:#D0D0D0\">"+resources.getString("login.remember")+"</body></html>", false);
rememberAccount.setFont(rememberFont);
rememberAccount.setForeground(LauncherFrame.COLOR_WHITE_TEXT);
rememberAccount.setOpaque(false);
rememberAccount.setHorizontalTextPosition(SwingConstants.LEFT);
rememberAccount.setHorizontalAlignment(SwingConstants.RIGHT);
rememberAccount.setBorder(BorderFactory.createEmptyBorder());
rememberAccount.setIconTextGap(6);
rememberAccount.addKeyListener(this);
rememberAccount.setSelectedIcon(resources.getIcon("checkbox_closed.png"));
rememberAccount.setIcon(resources.getIcon("checkbox_open.png"));
rememberAccount.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
toggleRemember();
}
});
rememberAccount.setFocusPainted(false);
add(rememberAccount, new GridBagConstraints(1,6,2,1,1.0,0.0, GridBagConstraints.EAST, GridBagConstraints.BOTH, new Insets(24,20,0,20),0,0));
//Login button
RoundedButton button = new RoundedButton(resources.getString("login.button"));
button.setBorder(BorderFactory.createEmptyBorder(5,17,10,17));
button.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 16));
button.setContentAreaFilled(false);
button.setForeground(LauncherFrame.COLOR_BUTTON_BLUE);
button.setHoverForeground(LauncherFrame.COLOR_BLUE);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
attemptLogin();
}
});
add(button, new GridBagConstraints(0, 6, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.NONE, new Insets(24,20,0,0),0,0));
add(Box.createVerticalGlue(), new GridBagConstraints(0, 8, 3, 1, 1.0, 1.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0,0,0,0),0,0));
JPanel linkPane = new JPanel();
linkPane.setBackground(LauncherFrame.COLOR_SELECTOR_BACK);
linkPane.setBorder(BorderFactory.createEmptyBorder(7,0,7,0));
linkPane.setLayout(new BoxLayout(linkPane, BoxLayout.LINE_AXIS));
linkPane.add(Box.createHorizontalStrut(8));
languages = new JComboBox();
String defaultLocaleText = resources.getString("launcheroptions.language.default");
if (!resources.isDefaultLocaleSupported()) {
defaultLocaleText = defaultLocaleText.concat(" (" + resources.getString("launcheroptions.language.unavailable") + ")");
}
languages.addItem(new LanguageItem(ResourceLoader.DEFAULT_LOCALE, defaultLocaleText, resources));
for (int i = 0; i < LauncherMain.supportedLanguages.length; i++) {
languages.addItem(new LanguageItem(resources.getCodeFromLocale(LauncherMain.supportedLanguages[i]), LauncherMain.supportedLanguages[i].getDisplayName(LauncherMain.supportedLanguages[i]), resources.getVariant(LauncherMain.supportedLanguages[i])));
}
if (!settings.getLanguageCode().equalsIgnoreCase(ResourceLoader.DEFAULT_LOCALE)) {
Locale loc = resources.getLocaleFromCode(settings.getLanguageCode());
for (int i = 0; i < LauncherMain.supportedLanguages.length; i++) {
if (loc.equals(LauncherMain.supportedLanguages[i])) {
languages.setSelectedIndex(i+1);
break;
}
}
}
languages.setBorder(BorderFactory.createEmptyBorder());
languages.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 14));
languages.setUI(new LanguageCellUI(resources, new RoundedBorderFormatter(new LineBorder(Color.black, 1)), LauncherFrame.COLOR_SCROLL_TRACK, LauncherFrame.COLOR_SCROLL_THUMB));
languages.setForeground(LauncherFrame.COLOR_WHITE_TEXT);
languages.setBackground(LauncherFrame.COLOR_SELECTOR_BACK);
languages.setRenderer(new LanguageCellRenderer(resources, "globe.png", LauncherFrame.COLOR_SELECTOR_BACK, LauncherFrame.COLOR_WHITE_TEXT));
languages.setEditable(false);
languages.setFocusable(false);
languages.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
languageChanged();
}
});
linkPane.add(languages);
linkPane.add(Box.createHorizontalGlue());
JButton termsLink = new JButton(resources.getString("login.terms"));
termsLink.setContentAreaFilled(false);
termsLink.setBorder(BorderFactory.createEmptyBorder());
termsLink.setForeground(LauncherFrame.COLOR_WHITE_TEXT);
termsLink.setFont(resources.getFont(ResourceLoader.FONT_OPENSANS, 14));
termsLink.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
termsLink.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
visitTerms();
}
});
linkPane.add(termsLink);
linkPane.add(Box.createHorizontalStrut(8));
add(linkPane, new GridBagConstraints(0, 9, 3, 1, 1.0, 0.0, GridBagConstraints.SOUTH, GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0));
}
@Override
public void relocalize(ResourceLoader loader) {
this.resources = loader;
this.resources.registerResource(this);
setIconImage(this.resources.getImage("icon.png"));
//Wipe controls
this.getContentPane().removeAll();
this.setLayout(null);
//Clear references to existing controls
nameSelect = null;
rememberAccount = null;
password = null;
initComponents();
refreshUsers();
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
invalidate();
repaint();
}
});
}
@Override
public void userChanged(MojangUser mojangUser) {
if (mojangUser == null) {
this.setVisible(true);
refreshUsers();
if (nameSelect.isVisible())
nameSelect.grabFocus();
else
name.grabFocus();
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
invalidate();
repaint();
}
});
} else
this.setVisible(false);
}
}