package esmska.gui;
import esmska.Context;
import esmska.data.Gateways.Events;
import esmska.data.event.ValuedEvent;
import esmska.gui.dnd.ImportContactsTransferHandler;
import esmska.data.Contact;
import esmska.data.Contacts;
import esmska.data.CountryPrefix;
import esmska.data.Icons;
import esmska.data.Log;
import esmska.data.Gateways;
import esmska.data.Gateway;
import esmska.data.event.ActionEventSupport;
import esmska.data.event.ValuedListener;
import esmska.utils.L10N;
import esmska.utils.MiscUtils;
import esmska.utils.RuntimeUtils;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Rectangle;
import java.awt.SystemColor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.ResourceBundle;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.AbstractListModel;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DropMode;
import javax.swing.GroupLayout;
import javax.swing.GroupLayout.Alignment;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.LayoutStyle.ComponentPlacement;
import javax.swing.ListCellRenderer;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.apache.commons.lang.StringUtils;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultListCellRenderer;
/** Contact list panel
*
* @author ripper
*/
public class ContactPanel extends javax.swing.JPanel {
public static final int ACTION_CONTACT_SELECTION_CHANGED = 0;
public static final int ACTION_CONTACT_CHOSEN = 1;
private static final String RES = "/esmska/resources/";
private static final Logger logger = Logger.getLogger(ContactPanel.class.getName());
private static final ResourceBundle l10n = L10N.l10nBundle;
private static final Contacts contacts = Contacts.getInstance();
private static final Log log = Log.getInstance();
private static final Gateways gateways = Gateways.getInstance();
private Action addContactAction = new AddContactAction(null);
private Action editContactAction = new EditContactAction();
private Action removeContactAction = new RemoveContactAction();
private Action chooseContactAction = new ChooseContactAction();
private SearchContactAction searchContactAction = new SearchContactAction();
private ContactListModel contactListModel = new ContactListModel();
private ContactPopupMenu popup = new ContactPopupMenu();
private ContactMouseListener mouseListener;
// <editor-fold defaultstate="collapsed" desc="ActionEvent support">
private ActionEventSupport actionSupport = new ActionEventSupport(this);
public void addActionListener(ActionListener actionListener) {
actionSupport.addActionListener(actionListener);
}
public void removeActionListener(ActionListener actionListener) {
actionSupport.removeActionListener(actionListener);
}
// </editor-fold>
/** Creates new form ContactPanel */
public ContactPanel() {
initComponents();
//add mouse listeners to the contact list
mouseListener = new ContactMouseListener(contactList, popup);
contactList.addMouseListener(mouseListener);
//add DnD support for contact list
contactList.setDropMode(DropMode.ON);
contactList.setTransferHandler(new ImportContactsTransferHandler());
//show new contact hint if there are no contacts
((ContactList)contactList).showNewContactHint(contacts.size() <= 0);
//listen for changes in contacts size and change hint visibility
contacts.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
((ContactList)contactList).showNewContactHint(
contacts.size() <= 0);
}
});
//listen for changes in gateways and repaint contacts if necessary
gateways.addValuedListener(new ValuedListener<Gateways.Events, Gateway>() {
@Override
public void eventOccured(ValuedEvent<Events, Gateway> e) {
switch(e.getEvent()) {
case ADDED_GATEWAY:
case ADDED_GATEWAYS:
case CLEARED_GATEWAYS:
case REMOVED_GATEWAY:
case REMOVED_GATEWAYS:
contactList.repaint();
}
}
});
}
/** clear selection of contact list */
public void clearSelection() {
contactList.clearSelection();
}
/** set selected contact in contact list */
public void setSelectedContact(Contact contact) {
contactList.setSelectedValue(contact, true);
}
/** set selected contact in contact list based on contact name
* @return true, if contact with same name was found, false otherwise
*/
public boolean setSelectedContact(String name) {
if (name == null || name.length() == 0) {
return false;
}
for (Contact c : contacts.getAll()) {
if (c.getName().equals(name)) {
contactList.setSelectedValue(c, true);
return true;
}
}
return false;
}
/** Return selected contacts
* @return Collection of selected contacts. Zero length collection if noone selected.
*/
public HashSet<Contact> getSelectedContacts() {
HashSet<Contact> selectedContacts = new HashSet<Contact>();
for (Object o : contactList.getSelectedValues()) {
selectedContacts.add((Contact) o);
}
return selectedContacts;
}
/** select first contact in contact list, if possible and no other contact selected */
public void ensureContactSelected() {
if (contactList.getSelectedIndex() < 0 && contactListModel.getSize() > 0) {
contactList.setSelectedIndex(0);
}
}
/** Add margins to selected contact to make selection nicer. Has effect only
* if single contact selected.
*/
public void makeNiceSelection() {
int[] indices = contactList.getSelectedIndices();
if (indices.length != 1) {
return;
}
setSelectedContactIndexWithMargins(indices[0]);
}
/** Shows dialog for adding contact with predefined values
* @param skeleton skeleton of contact to show as default values; may be null
*/
public void showAddContactDialog(Contact skeleton) {
AddContactAction action = new AddContactAction(skeleton);
action.actionPerformed(null);
}
/** sets selected index in contact list with making intelligent
* margins of 3 other contacts visible around the selected one
*/
private void setSelectedContactIndexWithMargins(int index) {
contactList.setSelectedIndex(index);
//let 3 contacts be visible before and after the selected contact
for (int j = index - 3; j <= index + 3; j++) {
if (j >= 0 && j < contactListModel.getSize()) {
contactList.ensureIndexIsVisible(j);
}
}
contactList.ensureIndexIsVisible(index);
}
/** This method is called from within the constructor to
* initialize the form.
* WARNING: Do NOT modify this code. The content of this method is
* always regenerated by the Form Editor.
*/
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
addContactButton = new JButton();
removeContactButton = new JButton();
jScrollPane4 = new JScrollPane();
contactList = new ContactList();
editContactButton = new JButton();
setBorder(BorderFactory.createTitledBorder(l10n.getString("ContactPanel.border.title"))); // NOI18N
addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent evt) {
formFocusGained(evt);
}
});
addContactButton.setAction(addContactAction);
addContactButton.setHideActionText(true);
addContactButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
addContactButton.setText(l10n.getString("Add"));
removeContactButton.setAction(removeContactAction);
removeContactButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
removeContactButton.setText("");
contactList.setModel(contactListModel);
contactList.setToolTipText(l10n.getString("ContactPanel.contactList.toolTipText")); // NOI18N
contactList.setCellRenderer(new ContactListRenderer());
String command = "choose contact";
contactList.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), command);
contactList.getActionMap().put(command, chooseContactAction);
command = "focus contacts";
getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
KeyStroke.getKeyStroke(KeyEvent.VK_K,KeyEvent.ALT_DOWN_MASK), command);
getActionMap().put(command, new AbstractAction() {
public void actionPerformed(ActionEvent e) {
contactList.requestFocusInWindow();
}
});
contactList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent evt) {
contactListValueChanged(evt);
}
});
contactList.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent evt) {
contactListKeyPressed(evt);
}
public void keyTyped(KeyEvent evt) {
contactListKeyTyped(evt);
}
});
jScrollPane4.setViewportView(contactList);
editContactButton.setAction(editContactAction);
editContactButton.putClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY, Boolean.TRUE);
editContactButton.setText("");
GroupLayout layout = new GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addComponent(addContactButton)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(editContactButton)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(removeContactButton))
.addComponent(jScrollPane4, GroupLayout.DEFAULT_SIZE, 242, Short.MAX_VALUE))
.addContainerGap())
);
layout.setVerticalGroup(
layout.createParallelGroup(Alignment.LEADING)
.addGroup(Alignment.TRAILING, layout.createSequentialGroup()
.addComponent(jScrollPane4, GroupLayout.DEFAULT_SIZE, 326, Short.MAX_VALUE)
.addPreferredGap(ComponentPlacement.RELATED)
.addGroup(layout.createParallelGroup(Alignment.TRAILING)
.addComponent(addContactButton)
.addComponent(editContactButton)
.addComponent(removeContactButton))
.addContainerGap())
);
}// </editor-fold>//GEN-END:initComponents
private void contactListValueChanged(ListSelectionEvent evt) {//GEN-FIRST:event_contactListValueChanged
if (evt.getValueIsAdjusting()) {
return;
}
// update components
int count = contactList.getSelectedIndices().length;
removeContactAction.setEnabled(count > 0);
editContactAction.setEnabled(count > 0);
//fire event
actionSupport.fireActionPerformed(ACTION_CONTACT_SELECTION_CHANGED, null);
}//GEN-LAST:event_contactListValueChanged
private void formFocusGained(FocusEvent evt) {//GEN-FIRST:event_formFocusGained
contactList.requestFocusInWindow();
}//GEN-LAST:event_formFocusGained
private void contactListKeyTyped(KeyEvent evt) {//GEN-FIRST:event_contactListKeyTyped
//do not catch keyboard shortcuts
if (evt.isActionKey() || evt.isAltDown() || evt.isAltGraphDown() ||
evt.isControlDown() || evt.isMetaDown()) {
return;
}
char chr = evt.getKeyChar();
//skip control characters (enter, etc)
if (Character.isISOControl(chr)) {
return;
}
//search
String searchString = searchContactAction.getSearchString();
searchString += Character.toLowerCase(chr);
searchContactAction.setSearchString(searchString);
searchContactAction.actionPerformed(null);
}//GEN-LAST:event_contactListKeyTyped
private void contactListKeyPressed(KeyEvent evt) {//GEN-FIRST:event_contactListKeyPressed
//delete last letter in search string on backspace
if (evt.getKeyCode() == KeyEvent.VK_BACK_SPACE) {
String searchString = searchContactAction.getSearchString();
if (searchString.length() > 0) {
searchString = searchString.substring(0, searchString.length() - 1);
searchContactAction.setSearchString(searchString);
searchContactAction.actionPerformed(null);
}
return;
}
//cancel search string on escape
if (evt.getKeyCode() == KeyEvent.VK_ESCAPE) {
searchContactAction.setSearchString("");
searchContactAction.actionPerformed(null);
return;
}
//move to another matching contact when searching and using arrows (and prolong the delay)
if ((evt.getKeyCode() == KeyEvent.VK_UP || evt.getKeyCode() == KeyEvent.VK_DOWN) &&
!searchContactAction.getSearchString().equals("")) {
int index = Math.max(contactList.getSelectedIndex(), 0);
if (evt.getKeyCode() == KeyEvent.VK_DOWN) { //go to next matching contact
index++;
for (; index < contactListModel.getSize(); index++) {
Contact contact = contactListModel.getElementAt(index);
if (searchContactAction.isContactMatched(contact)) {
setSelectedContactIndexWithMargins(index);
break;
}
}
} else { //go to previous matching contact
index--;
for (; index >= 0; index--) {
Contact contact = contactListModel.getElementAt(index);
if (searchContactAction.isContactMatched(contact)) {
setSelectedContactIndexWithMargins(index);
break;
}
}
}
evt.consume();
searchContactAction.restartTimer();
((ContactList)contactList).repaintSearchField();
}
//delete contact on delete
if (evt.getKeyCode() == KeyEvent.VK_DELETE) {
removeContactButton.doClick(0);
return;
}
}//GEN-LAST:event_contactListKeyPressed
/** Add contact to contact list */
private class AddContactAction extends AbstractAction {
private final String createOption = l10n.getString("Create");
private final String cancelOption = l10n.getString("Cancel");
private final Object[] options = RuntimeUtils.sortDialogOptions(
cancelOption, createOption);
private final Contact skeleton;
/** Constructor
* @param skeleton skeleton of contact to show as default values; may be null
*/
public AddContactAction(Contact skeleton) {
super(l10n.getString("Add_contact"), Icons.get("add-16.png"));
this.putValue(SHORT_DESCRIPTION,l10n.getString("Add_new_contact"));
this.putValue(LARGE_ICON_KEY, Icons.get("add-22.png"));
this.putValue(MNEMONIC_KEY, KeyEvent.VK_A);
this.skeleton = skeleton;
}
@Override
public void actionPerformed(ActionEvent e) {
contactList.requestFocusInWindow(); //always transfer focus
ContactDialog contactDialog = new ContactDialog();
contactDialog.setTitle(l10n.getString("New_contact"));
contactDialog.setOptions(options, createOption, createOption);
contactDialog.show(skeleton);
Contact c = contactDialog.getContact();
if (c == null) {
return;
}
contacts.add(c);
contactList.setSelectedValue(c, true);
log.addRecord(new Log.Record(
MessageFormat.format(l10n.getString("ContactPanel.addedContact"), c.getName()),
null, Icons.STATUS_INFO));
}
}
/** Edit contact from contact list */
private class EditContactAction extends AbstractAction {
private final String saveOption = l10n.getString("Save");
private final String cancelOption = l10n.getString("Cancel");
private final Object[] options = RuntimeUtils.sortDialogOptions(
cancelOption, saveOption);
public EditContactAction() {
super(l10n.getString("Edit_contacts"), Icons.get("edit-16.png"));
this.putValue(SHORT_DESCRIPTION,l10n.getString("Edit_selected_contacts"));
this.putValue(LARGE_ICON_KEY, Icons.get("edit-22.png"));
this.setEnabled(false);
}
@Override
public void actionPerformed(ActionEvent e) {
contactList.requestFocusInWindow(); //always transfer focus
Object[] selected = contactList.getSelectedValues();
if (selected.length <= 0) {
logger.warning("Trying to edit contact when none selected");
return;
}
ContactDialog contactDialog = new ContactDialog();
contactDialog.setOptions(options, saveOption, saveOption);
if (selected.length == 1) { //edit single contact
Contact contact = (Contact) selected[0];
contactDialog.setTitle(l10n.getString("Edit_contact"));
Contact edited = new Contact(contact);
contactDialog.show(edited);
edited = contactDialog.getContact();
if (edited == null) {
return;
}
contact.copyFrom(edited);
contactList.setSelectedValue(contact, true);
log.addRecord(new Log.Record(
MessageFormat.format(l10n.getString("ContactPanel.editedContact"), contact.getName()),
null, Icons.STATUS_INFO));
} else { //multiple contacts edited
contactDialog.setTitle(l10n.getString("Edit_contacts_collectively"));
ArrayList<Contact> list = new ArrayList<Contact>(selected.length);
for (Object contact : selected) {
list.add((Contact) contact);
}
contactDialog.show(list);
Contact c = contactDialog.getContact();
if (c == null) {
return;
}
int[] selection = contactList.getSelectedIndices();
for (Contact contact : list) {
//only gateway is common for all contacts
contact.setGateway(c.getGateway());
}
contactList.setSelectedIndices(selection);
log.addRecord(new Log.Record(
MessageFormat.format(l10n.getString("ContactPanel.editedContacts"), list.size()),
null, Icons.STATUS_INFO));
}
}
}
/** Remove contact from contact list */
private class RemoveContactAction extends AbstractAction {
private final String deleteOption = l10n.getString("Delete");
private final String cancelOption = l10n.getString("Cancel");
private final Object[] options = RuntimeUtils.sortDialogOptions(
cancelOption, deleteOption);
public RemoveContactAction() {
super(l10n.getString("Delete_contacts"), Icons.get("delete-16.png"));
this.putValue(SHORT_DESCRIPTION,l10n.getString("Delete_selected_contacts"));
this.putValue(LARGE_ICON_KEY, Icons.get("delete-22.png"));
this.setEnabled(false);
}
@Override
public void actionPerformed(ActionEvent e) {
contactList.requestFocusInWindow(); //always transfer focus
HashSet<Contact> condemned = getSelectedContacts();
//create warning
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JLabel label = new JLabel(l10n.getString("ContactPanel.remove_following_contacts"));
JTextArea area = new JTextArea();
area.setEditable(false);
area.setRows(5);
for (Contact c : condemned) {
area.append(c.getName() + "\n");
}
area.setCaretPosition(0);
panel.add(label, BorderLayout.PAGE_START);
panel.add(new JScrollPane(area), BorderLayout.CENTER);
//confirm
JOptionPane pane = new JOptionPane(panel, JOptionPane.WARNING_MESSAGE,
JOptionPane.DEFAULT_OPTION, null, options, deleteOption);
JDialog dialog = pane.createDialog(Context.mainFrame, null);
dialog.setResizable(true);
RuntimeUtils.setDocumentModalDialog(dialog);
dialog.pack();
dialog.setVisible(true);
//return if should not delete
if (!deleteOption.equals(pane.getValue())) {
return;
}
//delete
contacts.removeAll(condemned);
String message;
if (condemned.size() == 1) {
message = MessageFormat.format(l10n.getString("ContactPanel.removeContact"),
condemned.iterator().next().getName());
} else {
message = MessageFormat.format(l10n.getString("ContactPanel.removeContacts"),
condemned.size());
}
log.addRecord(new Log.Record(message, null, Icons.STATUS_INFO));
}
}
/** Choose contact in contact list by keyboard or mouse */
private class ChooseContactAction extends AbstractAction {
@Override
public void actionPerformed(ActionEvent e) {
if (contactList.getSelectedIndex() >= 0) {
actionSupport.fireActionPerformed(ACTION_CONTACT_CHOSEN, null);
}
}
}
/** Search for contact in contact list */
private class SearchContactAction extends AbstractAction {
private String searchString = "";
/** "forgetting" timer, time to forget the searched string */
private Timer timer = new Timer(2000, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
searchString = "";
SearchContactAction.this.actionPerformed(null);
}
});
public SearchContactAction() {
timer.setRepeats(false);
}
/** update the graphical highlighting */
private void updateRendering() {
((ContactList)contactList).showSearchField(searchString);
contactList.repaint();
}
/** do the search */
@Override
public void actionPerformed(ActionEvent e) {
if (searchString.equals("")) {
updateRendering();
return;
}
for (int i = 0; i < contactListModel.getSize(); i++) {
Contact contact = contactListModel.getElementAt(i);
if (isContactMatched(contact)) {
setSelectedContactIndexWithMargins(i);
break;
}
}
updateRendering();
restartTimer();
}
/** @return true if contact is matched by search string, false otherwise */
public boolean isContactMatched(Contact contact) {
if (searchString.equals("")) {
return true;
}
String name = contact.getName().toLowerCase();
return (name.contains(searchString) ||
MiscUtils.removeAccents(name).contains(searchString) ||
contact.getNumber().contains(searchString));
}
/** set string to be searched in contact list */
public void setSearchString(String searchString) {
this.searchString = StringUtils.lowerCase(searchString);
}
/** get string searched in contact list */
public String getSearchString() {
return searchString;
}
/** force the search timer to restart (therefore prolong the delay) */
public void restartTimer() {
timer.restart();
}
}
/** JList with contacts */
private class ContactList extends JList {
JTextField searchField = new JTextField();
JLabel newContactLabel = new JLabel(l10n.getString("ContactPanel.new_contact_hint"));
public ContactList() {
searchField.setFocusable(false);
newContactLabel.setVerticalAlignment(JLabel.TOP);
newContactLabel.setForeground(SystemColor.textInactiveText);
//listen for changes in contacts and adjust selection accordingly
Contacts.getInstance().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int index = getSelectedIndex();
switch (e.getID()) {
case Contacts.ACTION_ADD_CONTACT:
case Contacts.ACTION_REMOVE_CONTACT:
case Contacts.ACTION_CLEAR_CONTACTS:
clearSelection();
break;
case Contacts.ACTION_CHANGE_CONTACT:
clearSelection();
setSelectedIndex(index);
break;
default:
logger.warning("Unknown action event type");
assert false : "Unknown action event type";
}
}
});
}
/** show search field in contact list or hide it
* @param text text to show; empty or null string hides the field
*/
public void showSearchField(String text) {
if (StringUtils.isEmpty(text)) {
remove(searchField);
} else {
searchField.setText(text);
if (searchField.getParent() == null) {
add(searchField);
}
}
searchField.invalidate();
validate();
}
/** Show hint how to add a new contact */
public void showNewContactHint(boolean show) {
if (show && newContactLabel.getParent() == null) {
add(newContactLabel);
} else {
remove(newContactLabel);
}
}
/** repaints only the search field, not the whole container */
public void repaintSearchField() {
Rectangle oldBounds = searchField.getBounds();
searchField.invalidate();
contactList.validate();
contactList.repaint(oldBounds); //repaint old bounds
contactList.repaint(searchField.getBounds()); //repaint new bounds
}
@Override
public void doLayout() {
super.doLayout();
if (searchField.getParent() != null) {
//place searchField to a lower right corner
Rectangle visibleRect = getVisibleRect();
int height = (int) searchField.getPreferredSize().getHeight();
//+1 bcz first char was cut off sometimes
int width = (int) searchField.getPreferredSize().getWidth() + 1;
searchField.setBounds(visibleRect.x + visibleRect.width - width,
visibleRect.y + visibleRect.height - height, width, height);
}
if (newContactLabel.getParent() != null) {
//place newContactLabel to the center 5px from all borders
Rectangle visibleRect = getVisibleRect();
int height = (int) visibleRect.height - 10;
int width = (int) visibleRect.width - 10;
newContactLabel.setBounds(visibleRect.x + 5,
visibleRect.y + 5, width, height);
}
}
@Override
public void updateUI() {
super.updateUI();
if (searchField != null) {
searchField.updateUI();
}
}
}
/** Model for contact list */
private class ContactListModel extends AbstractListModel {
private int oldSize = getSize();
public ContactListModel() {
//listen for changes in contacts and fire events accordingly
contacts.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
switch (e.getID()) {
case Contacts.ACTION_ADD_CONTACT:
case Contacts.ACTION_CHANGE_CONTACT:
fireContentsChanged(ContactListModel.this, 0, getSize());
break;
case Contacts.ACTION_REMOVE_CONTACT:
case Contacts.ACTION_CLEAR_CONTACTS:
fireIntervalRemoved(ContactListModel.this, 0, oldSize);
break;
default:
logger.warning("Unknown action event type");
assert false : "Unknown action event type";
}
oldSize = getSize();
}
});
}
@Override
public int getSize() {
return contacts.size();
}
@Override
public Contact getElementAt(int index) {
return contacts.getAll().toArray(new Contact[0])[index];
}
}
/** dialog for creating and editing contact */
private class ContactDialog extends JDialog implements PropertyChangeListener {
private final ImageIcon contactIcon = Icons.get("contact-48.png");
private EditContactPanel panel;
private JOptionPane optionPane;
private Contact contact;
private Object[] options;
private Object initialValue, confirmOption;
public ContactDialog() {
super((JFrame) SwingUtilities.getAncestorOfClass(JFrame.class, ContactPanel.this),
l10n.getString("Contact"), true);
//integrate modal window better on Mac, must be called before initialization
RuntimeUtils.setDocumentModalDialog(this);
init();
setDefaultCloseOperation(HIDE_ON_CLOSE);
//handle closing by user
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent evt) {
formWindowClosing(evt);
}
});
}
private void init() {
panel = new EditContactPanel();
optionPane = new JOptionPane(panel, JOptionPane.QUESTION_MESSAGE,
JOptionPane.DEFAULT_OPTION, contactIcon, options, initialValue);
optionPane.addPropertyChangeListener(this);
setContentPane(optionPane);
pack();
panel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getID() == ActionEventSupport.ACTION_NEED_RESIZE) {
ContactDialog.this.pack();
}
}
});
}
/** Set options to display as buttons
* @param options possible options
* @param initialValue default option
* @param confirmOption option which confirms the dialog; other options cancels it.
* Can't be null.
*/
public void setOptions(Object[] options, Object initialValue, Object confirmOption) {
this.options = options;
this.initialValue = initialValue;
this.confirmOption = confirmOption;
}
/** Show dialog with existing or new (null) contact */
public void show(Contact contact) {
logger.fine("Showing edit contact dialog for contact: " + contact);
this.contact = contact;
init();
setLocationRelativeTo(Context.mainFrame);
optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
panel.setContact(contact);
panel.prepareForShow();
setVisible(true);
}
/** Show dialog for editing multiple contacts. May not be null. */
public void show(Collection<Contact> contacts) {
if (contacts.size() <= 1) {
show(contacts.size() <= 0 ? null : contacts.iterator().next());
return;
}
logger.fine("Showing edit contact dialog for " + contacts.size() + " contacts");
this.contact = null;
init();
setLocationRelativeTo(Context.mainFrame);
optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
panel.setContacts(contacts);
panel.prepareForShow();
setVisible(true);
}
@Override
public void propertyChange(PropertyChangeEvent e) {
String prop = e.getPropertyName();
if (isVisible() && e.getSource() == optionPane && JOptionPane.VALUE_PROPERTY.equals(prop)) {
Object value = optionPane.getValue();
if (value == JOptionPane.UNINITIALIZED_VALUE) {
//ignore reset
return;
}
if (!value.equals(confirmOption)) { //not confirmed
contact = null;
setVisible(false);
return;
}
//verify inputs
if (!panel.validateForm()) {
optionPane.setValue(JOptionPane.UNINITIALIZED_VALUE);
return;
}
//inputs verified, all ok
contact = panel.getContact();
setVisible(false);
}
}
/** Get currently displayed contact. May be null (cancelled by user). */
public Contact getContact() {
return contact;
}
/** Respond to user closing */
private void formWindowClosing(WindowEvent evt) {
if (evt == null) {
//window closed programatically
return;
}
//not confirmed
contact = null;
}
}
/** Renderer for items in contact list */
private class ContactListRenderer extends SubstanceDefaultListCellRenderer {
private final ListCellRenderer lafRenderer = new JList().getCellRenderer();
private final URL contactIconURI = getClass().getResource(RES + "contact-32.png");
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
Component c = lafRenderer.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
Contact contact = (Contact)value;
JLabel label = ((JLabel)c);
//add gateway logo
Gateway gateway = gateways.get(contact.getGateway());
label.setIcon(gateway != null && !gateway.isHidden() ?
gateway.getIcon() : Icons.GATEWAY_BLANK);
//set tooltip
String tooltip = "<html><table><tr><td><img src=\"" + contactIconURI +
"\"></td><td valign=top><b>" + MiscUtils.escapeHtml(contact.getName()) +
"</b><br>" + CountryPrefix.stripCountryPrefix(contact.getNumber(), true) +
"<br>" + MiscUtils.escapeHtml(contact.getGateway()) +
"</td></tr></table></html>";
label.setToolTipText(tooltip);
//set background on non-matching contacts when searching
if (!searchContactAction.getSearchString().equals("") &&
!searchContactAction.isContactMatched(contact)) {
label.setBackground(label.getBackground().darker());
label.setForeground(label.getForeground().darker());
}
return label;
}
}
/** Popup menu in the contact list */
private class ContactPopupMenu extends JPopupMenu {
public ContactPopupMenu() {
JMenuItem menuItem = null;
//add contact action
menuItem = new JMenuItem(addContactAction);
this.add(menuItem);
//edit contact action
menuItem = new JMenuItem(editContactAction);
this.add(menuItem);
//remove contact action
menuItem = new JMenuItem(removeContactAction);
this.add(menuItem);
}
}
/** Mouse listener on the contact list */
private class ContactMouseListener extends ListPopupMouseListener {
public ContactMouseListener(JList list, JPopupMenu popup) {
super(list, popup);
}
@Override
public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() > 1) {
//edit contact on left button doubleclick
editContactAction.actionPerformed(null);
} else if (SwingUtilities.isMiddleMouseButton(e)) {
//transfer focus on middleclick
//if user clicked on unselected item, select it
int index = list.locationToIndex(e.getPoint());
if (index >= 0 && !list.isSelectedIndex(index)) {
list.setSelectedIndex(index);
}
chooseContactAction.actionPerformed(null);
}
}
}
// Variables declaration - do not modify//GEN-BEGIN:variables
private JButton addContactButton;
private JList contactList;
private JButton editContactButton;
private JScrollPane jScrollPane4;
private JButton removeContactButton;
// End of variables declaration//GEN-END:variables
}