OSDN Git Service

Merge branch 'Branch_release-'
[jindolf/Jindolf.git] / src / main / java / jp / sfjp / jindolf / view / AccountPanel.java
-/*\r
- * Account panel\r
- *\r
- * Copyright(c) 2008 olyutorskii\r
- * $Id: AccountPanel.java 956 2009-12-13 15:14:07Z olyutorskii $\r
- */\r
-\r
-package jp.sourceforge.jindolf;\r
-\r
-import java.awt.BorderLayout;\r
-import java.awt.Container;\r
-import java.awt.Frame;\r
-import java.awt.GridBagConstraints;\r
-import java.awt.GridBagLayout;\r
-import java.awt.Insets;\r
-import java.awt.event.ActionEvent;\r
-import java.awt.event.ActionListener;\r
-import java.awt.event.ItemEvent;\r
-import java.awt.event.ItemListener;\r
-import java.io.IOException;\r
-import java.util.HashMap;\r
-import java.util.Map;\r
-import javax.swing.BorderFactory;\r
-import javax.swing.JButton;\r
-import javax.swing.JComboBox;\r
-import javax.swing.JComponent;\r
-import javax.swing.JDialog;\r
-import javax.swing.JLabel;\r
-import javax.swing.JOptionPane;\r
-import javax.swing.JPanel;\r
-import javax.swing.JPasswordField;\r
-import javax.swing.JSeparator;\r
-import javax.swing.JTextArea;\r
-import javax.swing.JTextField;\r
-import javax.swing.border.Border;\r
-import jp.sourceforge.jindolf.corelib.LandState;\r
-\r
-/**\r
- * ログインパネル。\r
- */\r
-@SuppressWarnings("serial")\r
-public class AccountPanel\r
-        extends JDialog\r
-        implements ActionListener, ItemListener{\r
-\r
-    private static final String FRAMETITLE =\r
-            "アカウント管理 - " + Jindolf.TITLE;\r
-\r
-    private final Map<Land, String> landUserIDMap =\r
-            new HashMap<Land, String>();\r
-    private final Map<Land, char[]> landPasswordMap =\r
-            new HashMap<Land, char[]>();\r
-\r
-    private final JComboBox landBox = new JComboBox();\r
-    private final JTextField idField = new JTextField(15);\r
-    private final JPasswordField pwField = new JPasswordField(15);\r
-    private final JButton loginButton = new JButton("ログイン");\r
-    private final JButton logoutButton = new JButton("ログアウト");\r
-    private final JButton closeButton = new JButton("閉じる");\r
-    private final JTextArea status = new JTextArea();\r
-\r
-    /**\r
-     * アカウントパネルを生成。\r
-     * @param owner フレームオーナー\r
-     * @param landsModel 国モデル\r
-     * @throws java.lang.NullPointerException 引数がnull\r
-     */\r
-    public AccountPanel(Frame owner, LandsModel landsModel)\r
-            throws NullPointerException{\r
-        super(owner, FRAMETITLE, true);\r
-\r
-        if(landsModel == null) throw new NullPointerException();\r
-        for(Land land : landsModel.getLandList()){\r
-            String userID = "";\r
-            char[] password = {};\r
-            this.landUserIDMap.put(land, userID);\r
-            this.landPasswordMap.put(land, password);\r
-            this.landBox.addItem(land);\r
-        }\r
-\r
-        GUIUtils.modifyWindowAttributes(this, true, false, true);\r
-\r
-        this.landBox.setToolTipText("アカウント管理する国を選ぶ");\r
-        this.idField.setToolTipText("IDを入力してください");\r
-        this.pwField.setToolTipText("パスワードを入力してください");\r
-\r
-        Monodizer.monodize(this.idField);\r
-        Monodizer.monodize(this.pwField);\r
-\r
-        this.idField.setMargin(new Insets(1, 4, 1, 4));\r
-        this.pwField.setMargin(new Insets(1, 4, 1, 4));\r
-\r
-        this.idField.setComponentPopupMenu(new TextPopup());\r
-\r
-        this.landBox.setEditable(false);\r
-        this.landBox.addItemListener(this);\r
-\r
-        this.status.setEditable(false);\r
-        this.status.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));\r
-        this.status.setRows(2);\r
-        this.status.setLineWrap(true);\r
-\r
-        this.loginButton.addActionListener(this);\r
-        this.logoutButton.addActionListener(this);\r
-        this.closeButton.addActionListener(this);\r
-\r
-        getRootPane().setDefaultButton(this.loginButton);\r
-\r
-        Container content = getContentPane();\r
-        GridBagLayout layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-        content.setLayout(layout);\r
-\r
-        constraints.gridwidth = GridBagConstraints.REMAINDER;\r
-        constraints.weightx = 1.0;\r
-        constraints.insets = new Insets(5, 5, 5, 5);\r
-\r
-        JComponent accountPanel = createCredential();\r
-        JComponent buttonPanel = createButtonPanel();\r
-\r
-        constraints.weighty = 0.0;\r
-        constraints.fill = GridBagConstraints.HORIZONTAL;\r
-        content.add(accountPanel, constraints);\r
-\r
-        Border border = BorderFactory.createTitledBorder("ログインステータス");\r
-        JPanel panel = new JPanel();\r
-        panel.setLayout(new BorderLayout());\r
-        panel.add(this.status, BorderLayout.CENTER);\r
-        panel.setBorder(border);\r
-\r
-        constraints.weighty = 1.0;\r
-        constraints.fill = GridBagConstraints.BOTH;\r
-        content.add(panel, constraints);\r
-\r
-        constraints.weighty = 0.0;\r
-        constraints.fill = GridBagConstraints.HORIZONTAL;\r
-        content.add(new JSeparator(), constraints);\r
-\r
-        content.add(buttonPanel, constraints);\r
-\r
-        preSelectActiveLand();\r
-\r
-        updateGUI();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 認証パネルを生成する。\r
-     * @return 認証パネル\r
-     */\r
-    private JComponent createCredential(){\r
-        JPanel credential = new JPanel();\r
-\r
-        GridBagLayout layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-\r
-        credential.setLayout(layout);\r
-\r
-        constraints.insets = new Insets(5, 5, 5, 5);\r
-        constraints.fill = GridBagConstraints.NONE;\r
-\r
-        constraints.anchor = GridBagConstraints.EAST;\r
-        credential.add(new JLabel("国名 :"), constraints);\r
-        constraints.anchor = GridBagConstraints.WEST;\r
-        credential.add(this.landBox, constraints);\r
-\r
-        constraints.gridy = 1;\r
-        constraints.anchor = GridBagConstraints.EAST;\r
-        constraints.weightx = 0.0;\r
-        constraints.fill = GridBagConstraints.NONE;\r
-        credential.add(new JLabel("ID :"), constraints);\r
-        constraints.anchor = GridBagConstraints.WEST;\r
-        constraints.weightx = 1.0;\r
-        constraints.fill = GridBagConstraints.HORIZONTAL;\r
-        credential.add(this.idField, constraints);\r
-\r
-        constraints.gridy = 2;\r
-        constraints.anchor = GridBagConstraints.EAST;\r
-        constraints.weightx = 0.0;\r
-        constraints.fill = GridBagConstraints.NONE;\r
-        credential.add(new JLabel("パスワード :"), constraints);\r
-        constraints.anchor = GridBagConstraints.WEST;\r
-        constraints.weightx = 1.0;\r
-        constraints.fill = GridBagConstraints.HORIZONTAL;\r
-        credential.add(this.pwField, constraints);\r
-\r
-        return credential;\r
-    }\r
-\r
-    /**\r
-     * ボタンパネルの作成。\r
-     * @return ボタンパネル\r
-     */\r
-    private JComponent createButtonPanel(){\r
-        JPanel buttonPanel = new JPanel();\r
-\r
-        GridBagLayout layout = new GridBagLayout();\r
-        GridBagConstraints constraints = new GridBagConstraints();\r
-\r
-        buttonPanel.setLayout(layout);\r
-\r
-        constraints.fill = GridBagConstraints.NONE;\r
-        constraints.anchor = GridBagConstraints.WEST;\r
-        constraints.weightx = 0.0;\r
-        constraints.weighty = 0.0;\r
-\r
-        buttonPanel.add(this.loginButton, constraints);\r
-\r
-        constraints.insets = new Insets(0, 5, 0, 0);\r
-        buttonPanel.add(this.logoutButton, constraints);\r
-\r
-        constraints.anchor = GridBagConstraints.EAST;\r
-        constraints.weightx = 1.0;\r
-        constraints.insets = new Insets(0, 15, 0, 0);\r
-        buttonPanel.add(this.closeButton, constraints);\r
-\r
-        return buttonPanel;\r
-    }\r
-\r
-    /**\r
-     * 現在コンボボックスで選択中の国を返す。\r
-     * @return 現在選択中のLand\r
-     */\r
-    private Land getSelectedLand(){\r
-        Land land = (Land)( this.landBox.getSelectedItem() );\r
-        return land;\r
-    }\r
-\r
-    /**\r
-     * ACTIVEな最初の国がコンボボックスで既に選択されている状態にする。\r
-     */\r
-    private void preSelectActiveLand(){\r
-        for(int index = 0; index < this.landBox.getItemCount(); index++){\r
-            Object item = this.landBox.getItemAt(index);\r
-            Land land = (Land) item;\r
-            LandState state = land.getLandDef().getLandState();\r
-            if(state == LandState.ACTIVE){\r
-                this.landBox.setSelectedItem(land);\r
-                return;\r
-            }\r
-        }\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 指定された国のユーザIDを返す。\r
-     * @param land 国\r
-     * @return ユーザID\r
-     */\r
-    private String getUserID(Land land){\r
-        return this.landUserIDMap.get(land);\r
-    }\r
-\r
-    /**\r
-     * 指定された国のパスワードを返す。\r
-     * @param land 国\r
-     * @return パスワード\r
-     */\r
-    private char[] getPassword(Land land){\r
-        return this.landPasswordMap.get(land);\r
-    }\r
-\r
-    /**\r
-     * ネットワークエラーを通知するモーダルダイアログを表示する。\r
-     * OKボタンを押すまでこのメソッドは戻ってこない。\r
-     * @param e ネットワークエラー\r
-     */\r
-    protected void showNetworkError(IOException e){\r
-        Jindolf.logger().warn(\r
-                "アカウント処理中にネットワークのトラブルが発生しました", e);\r
-\r
-        Land land = getSelectedLand();\r
-        ServerAccess server = land.getServerAccess();\r
-        String message =\r
-                land.getLandDef().getLandName()\r
-                +"を運営するサーバとの間の通信で"\r
-                +"何らかのトラブルが発生しました。\n"\r
-                +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n"\r
-                +"Webブラウザでも遊べないか確認してみてね!\n";\r
-\r
-        JOptionPane pane = new JOptionPane(message,\r
-                                           JOptionPane.WARNING_MESSAGE,\r
-                                           JOptionPane.DEFAULT_OPTION );\r
-\r
-        JDialog dialog = pane.createDialog(this,\r
-                                           "通信異常発生 - " + Jindolf.TITLE);\r
-\r
-        dialog.pack();\r
-        dialog.setVisible(true);\r
-        dialog.dispose();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * アカウントエラーを通知するモーダルダイアログを表示する。\r
-     * OKボタンを押すまでこのメソッドは戻ってこない。\r
-     */\r
-    protected void showIllegalAccountDialog(){\r
-        Land land = getSelectedLand();\r
-        String message =\r
-                land.getLandDef().getLandName()\r
-                +"へのログインに失敗しました。\n"\r
-                +"ユーザ名とパスワードは本当に正しいかな?\n"\r
-                +"あなたは本当に [ " + getUserID(land) + " ] さんかな?\n"\r
-                +"WebブラウザによるID登録手続きは本当に完了してるかな?\n"\r
-                +"Webブラウザでもログインできないか試してみて!\n"\r
-                +"…ユーザ名やパスワードにある種の特殊文字を使っている人は"\r
-                +"問題があるかも。";\r
-\r
-        JOptionPane pane = new JOptionPane(message,\r
-                                           JOptionPane.WARNING_MESSAGE,\r
-                                           JOptionPane.DEFAULT_OPTION );\r
-\r
-        JDialog dialog =\r
-                pane.createDialog(this, "ログイン認証失敗 - " + Jindolf.TITLE);\r
-\r
-        dialog.pack();\r
-        dialog.setVisible(true);\r
-        dialog.dispose();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 入力されたアカウント情報を基に現在選択中の国へログインする。\r
-     * @return ログインに成功すればtrueを返す。\r
-     */\r
-    protected boolean login(){\r
-        Land land = getSelectedLand();\r
-        ServerAccess server = land.getServerAccess();\r
-\r
-        String id = this.idField.getText();\r
-        char[] password = this.pwField.getPassword();\r
-        this.landUserIDMap.put(land, id);\r
-        this.landPasswordMap.put(land, password);\r
-\r
-        boolean result = false;\r
-        try{\r
-            result = server.login(id, password);\r
-        }catch(IOException e){\r
-            showNetworkError(e);\r
-            return false;\r
-        }\r
-\r
-        if( ! result ){\r
-            showIllegalAccountDialog();\r
-        }\r
-\r
-        return result;\r
-    }\r
-\r
-    /**\r
-     * 現在選択中の国からログアウトする。\r
-     */\r
-    protected void logout(){\r
-        try{\r
-            logoutInternal();\r
-        }catch(IOException e){\r
-            showNetworkError(e);\r
-        }\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 現在選択中の国からログアウトする。\r
-     * @throws java.io.IOException ネットワークエラー\r
-     */\r
-    protected void logoutInternal() throws IOException{\r
-        Land land = getSelectedLand();\r
-        ServerAccess server = land.getServerAccess();\r
-        server.logout();\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * 現在選択中の国のログイン状態に合わせてGUIを更新する。\r
-     */\r
-    private void updateGUI(){\r
-        Land land = getSelectedLand();\r
-        LandState state = land.getLandDef().getLandState();\r
-        ServerAccess server = land.getServerAccess();\r
-        boolean hasLoggedIn = server.hasLoggedIn();\r
-\r
-        if(state != LandState.ACTIVE){\r
-            this.status.setText(\r
-                     "この国は既に募集を停止しました。\n"\r
-                    +"ログインは無意味です" );\r
-            this.idField.setEnabled(false);\r
-            this.pwField.setEnabled(false);\r
-            this.loginButton.setEnabled(false);\r
-            this.logoutButton.setEnabled(false);\r
-        }else if(hasLoggedIn){\r
-            this.status.setText("ユーザ [ " + getUserID(land) + " ] として\n"\r
-                          +"現在ログイン中です");\r
-            this.idField.setEnabled(false);\r
-            this.pwField.setEnabled(false);\r
-            this.loginButton.setEnabled(false);\r
-            this.logoutButton.setEnabled(true);\r
-        }else{\r
-            this.status.setText("現在ログインしていません");\r
-            this.idField.setEnabled(true);\r
-            this.pwField.setEnabled(true);\r
-            this.loginButton.setEnabled(true);\r
-            this.logoutButton.setEnabled(false);\r
-        }\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     * ボタン操作のリスナ。\r
-     * @param event イベント {@inheritDoc}\r
-     */\r
-    // TODO Return キー押下によるログインもサポートしたい\r
-    public void actionPerformed(ActionEvent event){\r
-        Object source = event.getSource();\r
-\r
-        if(source == this.closeButton){\r
-            setVisible(false);\r
-            dispose();\r
-            return;\r
-        }\r
-\r
-        if(source == this.loginButton){\r
-            login();\r
-        }else if(source == this.logoutButton){\r
-            logout();\r
-        }\r
-\r
-        updateGUI();\r
-\r
-        return;\r
-    }\r
-\r
-    /**\r
-     * {@inheritDoc}\r
-     * コンボボックス操作のリスナ。\r
-     * @param event イベント {@inheritDoc}\r
-     */\r
-    public void itemStateChanged(ItemEvent event){\r
-        Object source = event.getSource();\r
-        if(source != this.landBox) return;\r
-\r
-        Land land = (Land) event.getItem();\r
-        String id;\r
-        char[] password;\r
-\r
-        switch(event.getStateChange()){\r
-        case ItemEvent.SELECTED:\r
-            id = getUserID(land);\r
-            password = getPassword(land);\r
-            this.idField.setText(id);\r
-            this.pwField.setText(new String(password));\r
-            updateGUI();\r
-            break;\r
-        case ItemEvent.DESELECTED:\r
-            id = this.idField.getText();\r
-            password = this.pwField.getPassword();\r
-            this.landUserIDMap.put(land, id);\r
-            this.landPasswordMap.put(land, password);\r
-            break;\r
-        default:\r
-            assert false;\r
-            return;\r
-        }\r
-\r
-        return;\r
-    }\r
-\r
-    // TODO IDかパスワードが空の場合はログインボタンを無効にしたい\r
-}\r
+/*
+ * Account panel
+ *
+ * License : The MIT License
+ * Copyright(c) 2008 olyutorskii
+ */
+
+package jp.sfjp.jindolf.view;
+
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Frame;
+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.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.swing.BorderFactory;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JPasswordField;
+import javax.swing.JSeparator;
+import javax.swing.JTextArea;
+import javax.swing.JTextField;
+import javax.swing.border.Border;
+import jp.sfjp.jindolf.VerInfo;
+import jp.sfjp.jindolf.data.Land;
+import jp.sfjp.jindolf.data.LandsModel;
+import jp.sfjp.jindolf.dxchg.TextPopup;
+import jp.sfjp.jindolf.net.ServerAccess;
+import jp.sfjp.jindolf.util.GUIUtils;
+import jp.sfjp.jindolf.util.Monodizer;
+import jp.sourceforge.jindolf.corelib.LandState;
+
+/**
+ * ログインパネル。
+ */
+@SuppressWarnings("serial")
+public class AccountPanel
+        extends JDialog
+        implements ActionListener, ItemListener{
+
+    private static final Logger LOGGER = Logger.getAnonymousLogger();
+
+
+    private final Map<Land, String> landUserIDMap =
+            new HashMap<>();
+    private final Map<Land, char[]> landPasswordMap =
+            new HashMap<>();
+
+    private final JComboBox<Land> landBox = new JComboBox<>();
+    private final JTextField idField = new JTextField(15);
+    private final JPasswordField pwField = new JPasswordField(15);
+    private final JButton loginButton = new JButton("ログイン");
+    private final JButton logoutButton = new JButton("ログアウト");
+    private final JButton closeButton = new JButton("閉じる");
+    private final JTextArea status = new JTextArea();
+
+    /**
+     * アカウントパネルを生成。
+     * @param owner フレームオーナー
+     */
+    @SuppressWarnings("LeakingThisInConstructor")
+    public AccountPanel(Frame owner){
+        super(owner);
+        setModal(true);
+
+        GUIUtils.modifyWindowAttributes(this, true, false, true);
+
+        this.landBox.setToolTipText("アカウント管理する国を選ぶ");
+        this.idField.setToolTipText("IDを入力してください");
+        this.pwField.setToolTipText("パスワードを入力してください");
+
+        Monodizer.monodize(this.idField);
+        Monodizer.monodize(this.pwField);
+
+        this.idField.setMargin(new Insets(1, 4, 1, 4));
+        this.pwField.setMargin(new Insets(1, 4, 1, 4));
+
+        this.idField.setComponentPopupMenu(new TextPopup());
+
+        this.landBox.setEditable(false);
+        this.landBox.addItemListener(this);
+
+        this.status.setEditable(false);
+        this.status.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+        this.status.setRows(2);
+        this.status.setLineWrap(true);
+
+        this.loginButton.addActionListener(this);
+        this.logoutButton.addActionListener(this);
+        this.closeButton.addActionListener(this);
+
+        getRootPane().setDefaultButton(this.loginButton);
+
+        Container content = getContentPane();
+        GridBagLayout layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+        content.setLayout(layout);
+
+        constraints.gridwidth = GridBagConstraints.REMAINDER;
+        constraints.weightx = 1.0;
+        constraints.insets = new Insets(5, 5, 5, 5);
+
+        JComponent accountPanel = createCredential();
+        JComponent buttonPanel = createButtonPanel();
+
+        constraints.weighty = 0.0;
+        constraints.fill = GridBagConstraints.HORIZONTAL;
+        content.add(accountPanel, constraints);
+
+        Border border = BorderFactory.createTitledBorder("ログインステータス");
+        JPanel panel = new JPanel();
+        panel.setLayout(new BorderLayout());
+        panel.add(this.status, BorderLayout.CENTER);
+        panel.setBorder(border);
+
+        constraints.weighty = 1.0;
+        constraints.fill = GridBagConstraints.BOTH;
+        content.add(panel, constraints);
+
+        constraints.weighty = 0.0;
+        constraints.fill = GridBagConstraints.HORIZONTAL;
+        content.add(new JSeparator(), constraints);
+
+        content.add(buttonPanel, constraints);
+
+        return;
+    }
+
+    /**
+     * 認証パネルを生成する。
+     * @return 認証パネル
+     */
+    private JComponent createCredential(){
+        JPanel credential = new JPanel();
+
+        GridBagLayout layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        credential.setLayout(layout);
+
+        constraints.insets = new Insets(5, 5, 5, 5);
+        constraints.fill = GridBagConstraints.NONE;
+
+        constraints.anchor = GridBagConstraints.EAST;
+        credential.add(new JLabel("国名 :"), constraints);
+        constraints.anchor = GridBagConstraints.WEST;
+        credential.add(this.landBox, constraints);
+
+        constraints.gridy = 1;
+        constraints.anchor = GridBagConstraints.EAST;
+        constraints.weightx = 0.0;
+        constraints.fill = GridBagConstraints.NONE;
+        credential.add(new JLabel("ID :"), constraints);
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.weightx = 1.0;
+        constraints.fill = GridBagConstraints.HORIZONTAL;
+        credential.add(this.idField, constraints);
+
+        constraints.gridy = 2;
+        constraints.anchor = GridBagConstraints.EAST;
+        constraints.weightx = 0.0;
+        constraints.fill = GridBagConstraints.NONE;
+        credential.add(new JLabel("パスワード :"), constraints);
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.weightx = 1.0;
+        constraints.fill = GridBagConstraints.HORIZONTAL;
+        credential.add(this.pwField, constraints);
+
+        return credential;
+    }
+
+    /**
+     * ボタンパネルの作成。
+     * @return ボタンパネル
+     */
+    private JComponent createButtonPanel(){
+        JPanel buttonPanel = new JPanel();
+
+        GridBagLayout layout = new GridBagLayout();
+        GridBagConstraints constraints = new GridBagConstraints();
+
+        buttonPanel.setLayout(layout);
+
+        constraints.fill = GridBagConstraints.NONE;
+        constraints.anchor = GridBagConstraints.WEST;
+        constraints.weightx = 0.0;
+        constraints.weighty = 0.0;
+
+        buttonPanel.add(this.loginButton, constraints);
+
+        constraints.insets = new Insets(0, 5, 0, 0);
+        buttonPanel.add(this.logoutButton, constraints);
+
+        constraints.anchor = GridBagConstraints.EAST;
+        constraints.weightx = 1.0;
+        constraints.insets = new Insets(0, 15, 0, 0);
+        buttonPanel.add(this.closeButton, constraints);
+
+        return buttonPanel;
+    }
+
+    /**
+     * 現在コンボボックスで選択中の国を返す。
+     * @return 現在選択中のLand
+     */
+    private Land getSelectedLand(){
+        Land land = (Land) ( this.landBox.getSelectedItem() );
+        return land;
+    }
+
+    /**
+     * ACTIVEな最初の国がコンボボックスで既に選択されている状態にする。
+     */
+    private void preSelectActiveLand(){
+        for(int index = 0; index < this.landBox.getItemCount(); index++){
+            Object item = this.landBox.getItemAt(index);
+            Land land = (Land) item;
+            LandState state = land.getLandDef().getLandState();
+            if(state == LandState.ACTIVE){
+                this.landBox.setSelectedItem(land);
+                return;
+            }
+        }
+        return;
+    }
+
+    /**
+     * 指定された国のユーザIDを返す。
+     * @param land 国
+     * @return ユーザID
+     */
+    private String getUserID(Land land){
+        return this.landUserIDMap.get(land);
+    }
+
+    /**
+     * 指定された国のパスワードを返す。
+     * @param land 国
+     * @return パスワード
+     */
+    private char[] getPassword(Land land){
+        return this.landPasswordMap.get(land);
+    }
+
+    /**
+     * ネットワークエラーを通知するモーダルダイアログを表示する。
+     * OKボタンを押すまでこのメソッドは戻ってこない。
+     * @param e ネットワークエラー
+     */
+    protected void showNetworkError(IOException e){
+        LOGGER.log(Level.WARNING,
+                "アカウント処理中にネットワークのトラブルが発生しました", e);
+
+        Land land = getSelectedLand();
+        ServerAccess server = land.getServerAccess();
+        String message =
+                land.getLandDef().getLandName()
+                +"を運営するサーバとの間の通信で"
+                +"何らかのトラブルが発生しました。\n"
+                +"相手サーバのURLは [ " + server.getBaseURL() + " ] だよ。\n"
+                +"Webブラウザでも遊べないか確認してみてね!\n";
+
+        JOptionPane pane = new JOptionPane(message,
+                                           JOptionPane.WARNING_MESSAGE,
+                                           JOptionPane.DEFAULT_OPTION );
+
+        String title = VerInfo.getFrameTitle("通信異常発生");
+        JDialog dialog = pane.createDialog(this, title);
+
+        dialog.pack();
+        dialog.setVisible(true);
+        dialog.dispose();
+
+        return;
+    }
+
+    /**
+     * アカウントエラーを通知するモーダルダイアログを表示する。
+     * OKボタンを押すまでこのメソッドは戻ってこない。
+     */
+    protected void showIllegalAccountDialog(){
+        Land land = getSelectedLand();
+        String message =
+                land.getLandDef().getLandName()
+                +"へのログインに失敗しました。\n"
+                +"ユーザ名とパスワードは本当に正しいかな?\n"
+                +"あなたは本当に [ " + getUserID(land) + " ] さんかな?\n"
+                +"WebブラウザによるID登録手続きは本当に完了してるかな?\n"
+                +"Webブラウザでもログインできないか試してみて!\n"
+                +"…ユーザ名やパスワードにある種の特殊文字を使っている人は"
+                +"問題があるかも。";
+
+        JOptionPane pane = new JOptionPane(message,
+                                           JOptionPane.WARNING_MESSAGE,
+                                           JOptionPane.DEFAULT_OPTION );
+
+        String title = VerInfo.getFrameTitle("ログイン認証失敗");
+        JDialog dialog = pane.createDialog(this, title);
+
+        dialog.pack();
+        dialog.setVisible(true);
+        dialog.dispose();
+
+        return;
+    }
+
+    /**
+     * 入力されたアカウント情報を基に現在選択中の国へログインする。
+     * @return ログインに成功すればtrueを返す。
+     */
+    protected boolean login(){
+        Land land = getSelectedLand();
+        ServerAccess server = land.getServerAccess();
+
+        String id = this.idField.getText();
+        char[] password = this.pwField.getPassword();
+        this.landUserIDMap.put(land, id);
+        this.landPasswordMap.put(land, password);
+
+        boolean result = false;
+        try{
+            result = server.login(id, password);
+        }catch(IOException e){
+            showNetworkError(e);
+            return false;
+        }
+
+        if( ! result ){
+            showIllegalAccountDialog();
+        }
+
+        return result;
+    }
+
+    /**
+     * 現在選択中の国からログアウトする。
+     */
+    protected void logout(){
+        try{
+            logoutInternal();
+        }catch(IOException e){
+            showNetworkError(e);
+        }
+        return;
+    }
+
+    /**
+     * 現在選択中の国からログアウトする。
+     * @throws java.io.IOException ネットワークエラー
+     */
+    protected void logoutInternal() throws IOException{
+        Land land = getSelectedLand();
+        ServerAccess server = land.getServerAccess();
+        server.logout();
+        return;
+    }
+
+    /**
+     * 現在選択中の国のログイン状態に合わせてGUIを更新する。
+     */
+    private void updateGUI(){
+        Land land = getSelectedLand();
+        if(land == null) return;
+
+        LandState state = land.getLandDef().getLandState();
+        ServerAccess server = land.getServerAccess();
+        boolean hasLoggedIn = server.hasLoggedIn();
+
+        if(state != LandState.ACTIVE){
+            this.status.setText(
+                     "この国は既に募集を停止しました。\n"
+                    +"ログインは無意味です" );
+            this.idField.setEnabled(false);
+            this.pwField.setEnabled(false);
+            this.loginButton.setEnabled(false);
+            this.logoutButton.setEnabled(false);
+        }else if(hasLoggedIn){
+            this.status.setText("ユーザ [ " + getUserID(land) + " ] として\n"
+                          +"現在ログイン中です");
+            this.idField.setEnabled(false);
+            this.pwField.setEnabled(false);
+            this.loginButton.setEnabled(false);
+            this.logoutButton.setEnabled(true);
+        }else{
+            this.status.setText("現在ログインしていません");
+            this.idField.setEnabled(true);
+            this.pwField.setEnabled(true);
+            this.loginButton.setEnabled(true);
+            this.logoutButton.setEnabled(false);
+        }
+
+        return;
+    }
+
+    /**
+     * 国情報を設定する。
+     * @param model 国情報
+     * @throws NullPointerException 引数がnull
+     */
+    public void setModel(LandsModel model) throws NullPointerException{
+        if(model == null) throw new NullPointerException();
+
+        this.landUserIDMap.clear();
+        this.landPasswordMap.clear();
+        this.landBox.removeAllItems();
+
+        for(Land land : model.getLandList()){
+            String userID = "";
+            char[] password = {};
+            this.landUserIDMap.put(land, userID);
+            this.landPasswordMap.put(land, password);
+            this.landBox.addItem(land);
+        }
+
+        preSelectActiveLand();
+        updateGUI();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * ボタン操作のリスナ。
+     * @param event イベント {@inheritDoc}
+     */
+    // TODO Return キー押下によるログインもサポートしたい
+    @Override
+    public void actionPerformed(ActionEvent event){
+        Object source = event.getSource();
+
+        if(source == this.closeButton){
+            setVisible(false);
+            dispose();
+            return;
+        }
+
+        if(source == this.loginButton){
+            login();
+        }else if(source == this.logoutButton){
+            logout();
+        }
+
+        updateGUI();
+
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     * コンボボックス操作のリスナ。
+     * @param event イベント {@inheritDoc}
+     */
+    @Override
+    public void itemStateChanged(ItemEvent event){
+        Object source = event.getSource();
+        if(source != this.landBox) return;
+
+        Land land = (Land) event.getItem();
+        String id;
+        char[] password;
+
+        switch(event.getStateChange()){
+        case ItemEvent.SELECTED:
+            id = getUserID(land);
+            password = getPassword(land);
+            this.idField.setText(id);
+            this.pwField.setText(new String(password));
+            updateGUI();
+            break;
+        case ItemEvent.DESELECTED:
+            id = this.idField.getText();
+            password = this.pwField.getPassword();
+            this.landUserIDMap.put(land, id);
+            this.landPasswordMap.put(land, password);
+            break;
+        default:
+            assert false;
+            return;
+        }
+
+        return;
+    }
+
+    // TODO IDかパスワードが空の場合はログインボタンを無効にしたい
+}