/*
* SK's Minecraft Launcher
* Copyright (C) 2010-2014 Albert Pham <http://www.sk89q.com> and contributors
* Please see LICENSE.txt for license information.
*/
package com.skcraft.launcher.dialog;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.skcraft.concurrency.ObservableFuture;
import com.skcraft.concurrency.ProgressObservable;
import com.skcraft.launcher.Configuration;
import com.skcraft.launcher.Launcher;
import com.skcraft.launcher.auth.*;
import com.skcraft.launcher.swing.*;
import com.skcraft.launcher.persistence.Persistence;
import com.skcraft.launcher.util.SwingExecutor;
import lombok.Getter;
import lombok.NonNull;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.concurrent.Callable;
import static com.skcraft.launcher.util.SharedLocale._;
/**
* The login dialog.
*/
public class LoginDialog extends JDialog {
private final Launcher launcher;
@Getter private final AccountList accounts;
@Getter private Session session;
private final JComboBox idCombo = new JComboBox();
private final JPasswordField passwordText = new JPasswordField();
private final JCheckBox rememberIdCheck = new JCheckBox(_("login.rememberId"));
private final JCheckBox rememberPassCheck = new JCheckBox(_("login.rememberPassword"));
private final JButton loginButton = new JButton(_("login.login"));
private final LinkButton recoverButton = new LinkButton(_("login.recoverAccount"));
private final JButton offlineButton = new JButton(_("login.playOffline"));
private final JButton cancelButton = new JButton(_("button.cancel"));
private final FormPanel formPanel = new FormPanel();
private final LinedBoxPanel buttonsPanel = new LinedBoxPanel(true);
/**
* Create a new login dialog.
*
* @param owner the owner
* @param launcher the launcher
*/
public LoginDialog(Window owner, @NonNull Launcher launcher) {
super(owner, ModalityType.DOCUMENT_MODAL);
this.launcher = launcher;
this.accounts = launcher.getAccounts();
setTitle(_("login.title"));
initComponents();
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setMinimumSize(new Dimension(420, 0));
setResizable(false);
pack();
setLocationRelativeTo(owner);
setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent event) {
removeListeners();
dispose();
}
});
}
private void removeListeners() {
idCombo.setModel(new DefaultComboBoxModel());
}
private void initComponents() {
idCombo.setModel(getAccounts());
updateSelection();
rememberIdCheck.setBorder(BorderFactory.createEmptyBorder());
rememberPassCheck.setBorder(BorderFactory.createEmptyBorder());
idCombo.setEditable(true);
idCombo.getEditor().selectAll();
loginButton.setFont(loginButton.getFont().deriveFont(Font.BOLD));
formPanel.addRow(new JLabel(_("login.idPassword")), idCombo);
formPanel.addRow(new JLabel(_("login.password")), passwordText);
formPanel.addRow(new JLabel(), rememberIdCheck);
formPanel.addRow(new JLabel(), rememberPassCheck);
buttonsPanel.setBorder(BorderFactory.createEmptyBorder(26, 13, 13, 13));
if (launcher.getConfig().isOfflineEnabled()) {
buttonsPanel.addElement(offlineButton);
buttonsPanel.addElement(Box.createHorizontalStrut(2));
}
buttonsPanel.addElement(recoverButton);
buttonsPanel.addGlue();
buttonsPanel.addElement(loginButton);
buttonsPanel.addElement(cancelButton);
add(formPanel, BorderLayout.CENTER);
add(buttonsPanel, BorderLayout.SOUTH);
getRootPane().setDefaultButton(loginButton);
passwordText.setComponentPopupMenu(TextFieldPopupMenu.INSTANCE);
idCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updateSelection();
}
});
idCombo.getEditor().getEditorComponent().addMouseListener(new PopupMouseAdapter() {
@Override
protected void showPopup(MouseEvent e) {
popupManageMenu(e.getComponent(), e.getX(), e.getY());
}
});
recoverButton.addActionListener(
ActionListeners.openURL(recoverButton, launcher.getProperties().getProperty("resetPasswordUrl")));
loginButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
prepareLogin();
}
});
offlineButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
setResult(new OfflineSession(launcher.getProperties().getProperty("offlinePlayerName")));
removeListeners();
dispose();
}
});
cancelButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
removeListeners();
dispose();
}
});
rememberPassCheck.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (rememberPassCheck.isSelected()) {
rememberIdCheck.setSelected(true);
}
}
});
rememberIdCheck.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!rememberIdCheck.isSelected()) {
rememberPassCheck.setSelected(false);
}
}
});
}
private void popupManageMenu(Component component, int x, int y) {
Object selected = idCombo.getSelectedItem();
JPopupMenu popup = new JPopupMenu();
JMenuItem menuItem;
if (selected != null && selected instanceof Account) {
final Account account = (Account) selected;
menuItem = new JMenuItem(_("login.forgetUser"));
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
accounts.remove(account);
Persistence.commitAndForget(accounts);
}
});
popup.add(menuItem);
if (!Strings.isNullOrEmpty(account.getPassword())) {
menuItem = new JMenuItem(_("login.forgetPassword"));
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
account.setPassword(null);
Persistence.commitAndForget(accounts);
}
});
popup.add(menuItem);
}
}
menuItem = new JMenuItem(_("login.forgetAllPasswords"));
menuItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (SwingHelper.confirmDialog(LoginDialog.this,
_("login.confirmForgetAllPasswords"),
_("login.forgetAllPasswordsTitle"))) {
accounts.forgetPasswords();
Persistence.commitAndForget(accounts);
}
}
});
popup.add(menuItem);
popup.show(component, x, y);
}
private void updateSelection() {
Object selected = idCombo.getSelectedItem();
if (selected != null && selected instanceof Account) {
Account account = (Account) selected;
String password = account.getPassword();
rememberIdCheck.setSelected(true);
if (!Strings.isNullOrEmpty(password)) {
rememberPassCheck.setSelected(true);
passwordText.setText(password);
} else {
rememberPassCheck.setSelected(false);
}
} else {
passwordText.setText("");
rememberIdCheck.setSelected(true);
rememberPassCheck.setSelected(false);
}
}
@SuppressWarnings("deprecation")
private void prepareLogin() {
Object selected = idCombo.getSelectedItem();
if (selected != null && selected instanceof Account) {
Account account = (Account) selected;
String password = passwordText.getText();
if (password == null || password.isEmpty()) {
SwingHelper.showErrorDialog(this, _("login.noPasswordError"), _("login.noPasswordTitle"));
} else {
if (rememberPassCheck.isSelected()) {
account.setPassword(password);
} else {
account.setPassword(null);
}
if (rememberIdCheck.isSelected()) {
accounts.add(account);
} else {
accounts.remove(account);
}
account.setLastUsed(new Date());
Persistence.commitAndForget(accounts);
attemptLogin(account, password);
}
} else {
SwingHelper.showErrorDialog(this, _("login.noLoginError"), _("login.noLoginTitle"));
}
}
private void attemptLogin(Account account, String password) {
LoginCallable callable = new LoginCallable(account, password);
ObservableFuture<Session> future = new ObservableFuture<Session>(
launcher.getExecutor().submit(callable), callable);
Futures.addCallback(future, new FutureCallback<Session>() {
@Override
public void onSuccess(Session result) {
setResult(result);
}
@Override
public void onFailure(Throwable t) {
}
}, SwingExecutor.INSTANCE);
ProgressDialog.showProgress(this, future, _("login.loggingInTitle"), _("login.loggingInStatus"));
SwingHelper.addErrorDialogCallback(this, future);
}
private void setResult(Session session) {
this.session = session;
removeListeners();
dispose();
}
public static Session showLoginRequest(Window owner, Launcher launcher) {
LoginDialog dialog = new LoginDialog(owner, launcher);
dialog.setVisible(true);
return dialog.getSession();
}
private class LoginCallable implements Callable<Session>,ProgressObservable {
private final Account account;
private final String password;
private LoginCallable(Account account, String password) {
this.account = account;
this.password = password;
}
@Override
public Session call() throws AuthenticationException, IOException, InterruptedException {
LoginService service = launcher.getLoginService();
List<? extends Session> identities = service.login(launcher.getProperties().getProperty("agentName"), account.getId(), password);
// The list of identities (profiles in Mojang terms) corresponds to whether the account
// owns the game, so we need to check that
if (identities.size() > 0) {
// Set offline enabled flag to true
Configuration config = launcher.getConfig();
if (!config.isOfflineEnabled()) {
config.setOfflineEnabled(true);
Persistence.commitAndForget(config);
}
Persistence.commitAndForget(getAccounts());
return identities.get(0);
} else {
throw new AuthenticationException("Minecraft not owned", _("login.minecraftNotOwnedError"));
}
}
@Override
public double getProgress() {
return -1;
}
@Override
public String getStatus() {
return _("login.loggingInStatus");
}
}
}