/*
* Jitsi, the OpenSource Java VoIP and Instant Messaging client.
*
* Copyright @ 2015 Atlassian Pty Ltd
*
* 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 net.java.sip.communicator.impl.gui.main.account;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;
import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.customcontrols.wizard.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.util.*;
import org.jitsi.service.resources.*;
import org.osgi.framework.*;
/**
* The <tt>NewAccountDialog</tt> is the dialog containing the form used to
* create a new account.
*
* @author Yana Stamcheva
* @author Lyubomir Marinov
*/
public class NewAccountDialog
extends SIPCommDialog
implements CreateAccountWindow,
ActionListener,
PropertyChangeListener
{
/**
* The <tt>Logger</tt> used by the <tt>NewAccountDialog</tt> class and its
* instances for logging output.
*/
private static final Logger logger
= Logger.getLogger(NewAccountDialog.class);
private final TransparentPanel accountPanel
= new TransparentPanel(new BorderLayout());
private final JComboBox networkComboBox = new JComboBox();
private final JButton advancedButton = new JButton(
GuiActivator.getResources().getI18NString("service.gui.ADVANCED"));
private final JButton addAccountButton = new JButton(
GuiActivator.getResources().getI18NString("service.gui.ADD"));
private final JButton cancelButton = new JButton(
GuiActivator.getResources().getI18NString("service.gui.CANCEL"));
private final EmptyAccountRegistrationWizard emptyWizard
= new EmptyAccountRegistrationWizard();
private static NewAccountDialog newAccountDialog;
private JTextArea errorMessagePane;
/**
* If account is signing-in ignore close.
*/
private boolean isCurrentlySigningIn = false;
/**
* Status label, show when connecting.
*/
private final JLabel statusLabel = new JLabel();
/**
* The container of the wizard.
*/
private final AccountRegWizardContainerImpl wizardContainer;
/**
* Creates the dialog and initializes the UI.
*/
public NewAccountDialog()
{
super(GuiActivator.getUIService().getMainFrame(), false);
ResourceManagementService resources = GuiActivator.getResources();
TransparentPanel mainPanel
= new TransparentPanel(new BorderLayout(5, 5));
TransparentPanel networkPanel
= new TransparentPanel(new BorderLayout());
JLabel networkLabel
= new JLabel(resources.getI18NString("service.gui.NETWORK"));
TransparentPanel rightButtonPanel
= new TransparentPanel(new FlowLayout(FlowLayout.RIGHT, 0, 0));
TransparentPanel buttonPanel
= new TransparentPanel(new BorderLayout());
String title = resources.getI18NString("service.gui.NEW_ACCOUNT");
if ((title != null) && title.endsWith("..."))
title = title.substring(0, title.length() - 3);
this.setTitle(title);
this.getContentPane().add(mainPanel);
mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15));
networkPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
JPanel statusPanel = new TransparentPanel(
new FlowLayout(FlowLayout.CENTER));
statusPanel.add(statusLabel);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
if (!ConfigurationUtils.isAdvancedAccountConfigDisabled())
{
buttonPanel.add(advancedButton, BorderLayout.WEST);
this.advancedButton.addActionListener(this);
}
buttonPanel.add(rightButtonPanel, BorderLayout.EAST);
buttonPanel.add(statusPanel, BorderLayout.CENTER);
rightButtonPanel.add(addAccountButton);
rightButtonPanel.add(cancelButton);
this.addAccountButton.addActionListener(this);
this.cancelButton.addActionListener(this);
mainPanel.add(networkPanel, BorderLayout.NORTH);
networkPanel.add(networkLabel, BorderLayout.WEST);
networkPanel.add(networkComboBox, BorderLayout.CENTER);
this.networkComboBox.setRenderer(new NetworkListCellRenderer());
this.networkComboBox.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
AccountRegistrationWizard wizard
= (AccountRegistrationWizard)
networkComboBox.getSelectedItem();
loadSelectedWizard(wizard);
}
});
mainPanel.add(accountPanel, BorderLayout.CENTER);
this.initNetworkList();
wizardContainer
= (AccountRegWizardContainerImpl)
GuiActivator.getUIService().getAccountRegWizardContainer();
/*
* XXX The wizardContainer will outlive this instance so it is essential
* to call removePropertyChangeListener on its model in order to prevent
* a leak of this instance.
*/
wizardContainer.getModel().addPropertyChangeListener(this);
}
/**
* Detects all currently registered protocol wizards so that we could fill
* the protocol/network combo with their graphical representation.
*/
private void initNetworkList()
{
// check for preferred wizard
String prefWName
= GuiActivator.getResources().getSettingsString(
"impl.gui.PREFERRED_ACCOUNT_WIZARD");
String preferredWizardName
= (prefWName != null && prefWName.length() > 0) ? prefWName : null;
Collection<ServiceReference<AccountRegistrationWizard>> accountWizardRefs;
try
{
accountWizardRefs
= GuiActivator.bundleContext.getServiceReferences(
AccountRegistrationWizard.class,
null);
}
catch (InvalidSyntaxException ex)
{
// this shouldn't happen since we're providing no parameter string
// but let's log just in case.
logger.error("Error while retrieving service refs", ex);
return;
}
// in case we found any, add them in this container.
if ((accountWizardRefs != null) && !accountWizardRefs.isEmpty())
{
if (logger.isDebugEnabled())
{
logger.debug(
"Found " + accountWizardRefs.size()
+ " already installed providers.");
}
// Create a list to sort the wizards
ArrayList<AccountRegistrationWizard> networksList
= new ArrayList<AccountRegistrationWizard>(
accountWizardRefs.size());
AccountRegistrationWizard prefWiz = null;
for (ServiceReference<AccountRegistrationWizard> serRef
: accountWizardRefs)
{
AccountRegistrationWizard wizard
= GuiActivator.bundleContext.getService(serRef);
networksList.add(wizard);
// is it the preferred protocol ?
if(preferredWizardName != null
&& wizard.getClass().getName().equals(
preferredWizardName))
{
prefWiz = wizard;
}
}
// Sort the list
Collections.sort(networksList,
new Comparator<AccountRegistrationWizard>()
{
public int compare(AccountRegistrationWizard arg0,
AccountRegistrationWizard arg1)
{
return arg0.getProtocolName().compareToIgnoreCase(
arg1.getProtocolName());
}
});
// Add the items in the combobox
for (int i=0; i<networksList.size(); i++)
{
networkComboBox.addItem(networksList.get(i));
}
//if we have a prefered wizard auto select it
if (prefWiz != null)
{
networkComboBox.setSelectedItem(prefWiz);
}
else//if we don't we send our empty page and let the wizard choose.
{
networkComboBox.insertItemAt(emptyWizard, 0);
networkComboBox.setSelectedItem(emptyWizard);
//disable the advanced and add buttons so that it would be
//clear for the user that they need to choose a network first
advancedButton.setEnabled(false);
addAccountButton.setEnabled(false);
}
}
}
/**
* This method gets called when a property is changed.
*
* @param evt A PropertyChangeEvent object describing the event source
* and the property that has changed.
*/
public void propertyChange(PropertyChangeEvent evt)
{
if(evt.getPropertyName().equals(
WizardModel.CANCEL_BUTTON_ENABLED_PROPERTY))
{
if(evt.getNewValue() instanceof Boolean)
cancelButton.setEnabled(
(Boolean)evt.getNewValue());
}
else if(evt.getPropertyName().equals(
WizardModel.NEXT_FINISH_BUTTON_ENABLED_PROPERTY))
{
if(evt.getNewValue() instanceof Boolean)
addAccountButton.setEnabled(
(Boolean)evt.getNewValue());
}
}
/**
* A custom cell renderer for the network combobox.
*/
private static class NetworkListCellRenderer
extends JLabel
implements ListCellRenderer
{
private static final long serialVersionUID = 0L;
public NetworkListCellRenderer()
{
setBorder(BorderFactory.createEmptyBorder(2, 5, 2, 5));
setOpaque(true);
}
public Component getListCellRendererComponent(JList list, Object value,
int index, boolean isSelected, boolean cellHasFocus)
{
AccountRegistrationWizard wizard
= (AccountRegistrationWizard) value;
if (isSelected)
{
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else
{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setText(wizard.getProtocolName());
byte[] icon = wizard.getIcon();
setIcon(
((icon != null) && (icon.length > 0))
? new ImageIcon(icon)
: null);
return this;
}
}
/**
* Loads the given wizard in the user interface.
*
* @param wizard the wizard to load
*/
private void loadSelectedWizard(AccountRegistrationWizard wizard)
{
accountPanel.removeAll();
TransparentPanel fixedWidthPanel = new TransparentPanel();
Dimension fixedWidthSize = new Dimension(430, 3);
fixedWidthPanel.setPreferredSize(fixedWidthSize);
fixedWidthPanel.setMinimumSize(fixedWidthSize);
fixedWidthPanel.setMaximumSize(fixedWidthSize);
this.accountPanel.add(fixedWidthPanel, BorderLayout.SOUTH);
JComponent simpleWizardForm = (JComponent) wizard.getSimpleForm(false);
simpleWizardForm.setOpaque(false);
accountPanel.add(simpleWizardForm);
//enable the add and advanced buttons if this is a real protocol
boolean isEmptyAccountRegistrationWizard
= (wizard instanceof EmptyAccountRegistrationWizard);
addAccountButton.setEnabled(!isEmptyAccountRegistrationWizard);
advancedButton.setEnabled(!isEmptyAccountRegistrationWizard);
if (!isEmptyAccountRegistrationWizard)
getRootPane().setDefaultButton(addAccountButton);
if(!wizard.isAdvancedConfigurationEnabled())
advancedButton.setVisible(false);
else if(!advancedButton.isVisible())
advancedButton.setVisible(true);
accountPanel.revalidate();
accountPanel.repaint();
this.pack();
}
/**
* Loads the given error message in the current dialog, by re-validating the
* content.
*
* @param errorMessage The error message to load.
*/
private void loadErrorMessage(String errorMessage)
{
if (errorMessagePane == null)
{
errorMessagePane = new JTextArea();
errorMessagePane.setLineWrap(true);
errorMessagePane.setWrapStyleWord(true);
errorMessagePane.setOpaque(false);
errorMessagePane.setForeground(Color.RED);
accountPanel.add(errorMessagePane, BorderLayout.NORTH);
if (isVisible())
pack();
//WORKAROUND: there's something wrong happening in this pack and
//components get cluttered, partially hiding the password text
// field. I am under the impression that this has something to do
// with the message pane preferred size being ignored (or being 0)
// which is why I am adding it's height to the dialog. It's quite
// ugly so please fix if you have something better in mind.
this.setSize(getWidth(), getHeight()+errorMessagePane.getHeight());
}
errorMessagePane.setText(errorMessage);
accountPanel.revalidate();
accountPanel.repaint();
}
/**
* Handles button actions.
* @param event the <tt>ActionEvent</tt> that notified us
*/
public void actionPerformed(ActionEvent event)
{
JButton sourceButton = (JButton) event.getSource();
AccountRegistrationWizard wizard
= (AccountRegistrationWizard) networkComboBox.getSelectedItem();
if (sourceButton.equals(advancedButton))
{
wizard.setModification(false);
wizardContainer.setTitle(
GuiActivator.getResources().getI18NString(
"service.gui.ACCOUNT_REGISTRATION_WIZARD"));
wizardContainer.setCurrentWizard(wizard);
wizardContainer.showDialog(false);
this.dispose();
}
else if (sourceButton.equals(addAccountButton))
{
startConnecting(wizardContainer);
new Thread(new ProtocolSignInThread(wizard)).start();
}
else if (sourceButton.equals(cancelButton))
{
this.dispose();
}
}
/**
* Shows the new account dialog.
*/
public static void showNewAccountDialog()
{
if (newAccountDialog == null)
newAccountDialog = new NewAccountDialog();
newAccountDialog.pack();
newAccountDialog.setVisible(true);
newAccountDialog.requestFocus();
}
/**
* Remove the newAccountDialog, when the window is closed.
* @param isEscaped indicates if the dialog has been escaped
*/
@Override
protected void close(boolean isEscaped)
{
if(isCurrentlySigningIn)
return;
dispose();
}
/**
* Remove the newAccountDialog on dispose.
*/
@Override
public void dispose()
{
if(isCurrentlySigningIn)
return;
if (newAccountDialog == this)
newAccountDialog = null;
wizardContainer.getModel().removePropertyChangeListener(this);
super.dispose();
}
/**
* Overrides set visible to disable closing dialog if currently signing-in.
*
* @param visible <tt>true</tt> to make this <tt>NewAccountDialog</tt>
* visible; otherwise, <tt>false</tt>
*/
@Override
public void setVisible(boolean visible)
{
if(isCurrentlySigningIn)
return;
super.setVisible(visible);
}
private void startConnecting(AccountRegWizardContainerImpl wizardContainer)
{
isCurrentlySigningIn = true;
advancedButton.setEnabled(false);
addAccountButton.setEnabled(false);
cancelButton.setEnabled(false);
statusLabel.setText(GuiActivator.getResources().getI18NString(
"service.gui.CONNECTING"));
setCursor(new Cursor(Cursor.WAIT_CURSOR));
}
private void stopConnecting(AccountRegWizardContainerImpl wizardContainer)
{
isCurrentlySigningIn = false;
statusLabel.setText("");
setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
advancedButton.setEnabled(true);
addAccountButton.setEnabled(true);
cancelButton.setEnabled(true);
}
/**
* Makes protocol operations in different thread.
*/
private class ProtocolSignInThread
implements Runnable
{
/**
* The wizard to use.
*/
private final AccountRegistrationWizard wizard;
/**
* Creates <tt>ProtocolSignInThread</tt>.
* @param wizard the wizard to use.
*/
ProtocolSignInThread(AccountRegistrationWizard wizard)
{
this.wizard = wizard;
}
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p/>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see Thread#run()
*/
public void run()
{
ProtocolProviderService protocolProvider;
try
{
if(wizard == emptyWizard)
{
loadErrorMessage(GuiActivator.getResources().getI18NString(
"service.gui.CHOOSE_NETWORK"));
}
protocolProvider = wizard.signin();
if (protocolProvider != null)
{
wizardContainer.saveAccountWizard(protocolProvider, wizard);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
stopConnecting(wizardContainer);
NewAccountDialog.this.dispose();
}
});
}
else
{
// no provider, maybe an error, stop connecting
// so we can proceed with the actions
stopConnecting(wizardContainer);
}
}
catch (OperationFailedException e)
{
// make sure buttons don't stay disabled
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
stopConnecting(wizardContainer);
}
});
// If the sign in operation has failed we don't want to close
// the dialog in order to give the user the possibility to
// retry.
if (logger.isDebugEnabled())
logger.debug("The sign in operation has failed.");
if (e.getErrorCode()
== OperationFailedException.ILLEGAL_ARGUMENT)
{
loadErrorMessage(GuiActivator.getResources().getI18NString(
"service.gui.USERNAME_NULL"));
}
else if (e.getErrorCode()
== OperationFailedException.IDENTIFICATION_CONFLICT)
{
loadErrorMessage(GuiActivator.getResources().getI18NString(
"service.gui.USER_EXISTS_ERROR"));
}
else if (e.getErrorCode()
== OperationFailedException.SERVER_NOT_SPECIFIED)
{
loadErrorMessage(GuiActivator.getResources().getI18NString(
"service.gui.SPECIFY_SERVER"));
}
else
{
loadErrorMessage(GuiActivator.getResources().getI18NString(
"service.gui.ACCOUNT_CREATION_FAILED",
new String[]{e.getMessage()}));
}
}
catch (Exception e)
{
logger.error("Error creating account: "+e.getMessage(), e);
// make sure buttons don't stay disabled
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
stopConnecting(wizardContainer);
}
});
// If the sign in operation has failed we don't want to close
// the dialog in order to give the user the possibility to
// retry.
if (logger.isDebugEnabled())
logger.debug("The sign in operation has failed.");
loadErrorMessage(GuiActivator.getResources().getI18NString(
"service.gui.ACCOUNT_CREATION_FAILED",
new String[]{e.getMessage()}));
}
}
}
/**
* Sets the selected wizard.
*
* @param wizard the wizard to select
* @param isCreatedForm indicates if the selected wizard should be opened
* in create account mode
*/
public void setSelectedWizard( AccountRegistrationWizard wizard,
boolean isCreateAccount)
{
networkComboBox.setSelectedItem(wizard);
}
}