/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2015 University of Dundee. All rights reserved.
*
*
* This program 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 2 of the License, or
* (at your option) any later version.
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.agents.util;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import org.apache.commons.collections.CollectionUtils;
import org.openmicroscopy.shoola.util.CommonsLangUtils;
import org.openmicroscopy.shoola.util.ui.IconManager;
import org.openmicroscopy.shoola.util.ui.TitlePanel;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.search.SearchUtil;
import omero.gateway.model.DataObject;
import omero.gateway.model.ExperimenterData;
import omero.gateway.model.GroupData;
import omero.gateway.model.TagAnnotationData;
/**
* A modal dialog to select collection of objects.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* @since OME3.0
*/
public class SelectionWizard
extends JDialog
implements ActionListener, DocumentListener, FocusListener,
PropertyChangeListener
{
/** Bound property indicating the selected items. */
public static final String SELECTED_ITEMS_PROPERTY = "selectedItems";
/** Bound property indicating to cancel the selection. */
public static final String CANCEL_SELECTION_PROPERTY = "cancelSelection";
/** The default text for the tag.*/
private static final String DEFAULT_TEXT = "Tag";
/** The default text for the tag's description.*/
private static final String DEFAULT_DESCRIPTION = "Description";
/** Action command ID to Accept the current field selection. */
private static final int ACCEPT = 0;
/** Action command ID to cancel the wizard. */
private static final int CANCEL = 1;
/** Action command ID to reset the current field selection. */
private static final int RESET = 2;
/** Action command ID to add new object to the selection. */
private static final int ADD_NEW = 3;
/** The default size. */
private static final Dimension DEFAULT_SIZE = new Dimension(700, 700);
/** The button to accept the current selection. */
private JButton acceptButton;
/** The button to reset the current selection. */
private JButton resetButton;
/** The button to cancel the current selection. */
private JButton cancelButton;
/** The type to handle. */
private Class<?> type;
/** Button to add new tag to the selection. */
private JButton addNewButton;
/** The component used to add a new object. */
private JTextField addField;
/** The component used to add a new description to the object. */
private JTextField descriptionField;
/** The component displaying the selection. */
private SelectionWizardUI uiDelegate;
/** The original color of a text field.*/
private Color originalColor;
/** The label displaying the message indicating what will be added.*/
private JLabel addLabel;
/** Sets the controls.*/
private void setControls()
{
String text = addField.getText();
addNewButton.setEnabled(CommonsLangUtils.isNotBlank(text) &&
!DEFAULT_TEXT.equals(text));
}
/**
* Sets the default text for the specified field.
*
* @param field The field to handle.
* @param text The text to display.
*/
private void setTextFieldDefault(JTextField field, String text)
{
field.getDocument().removeDocumentListener(this);
if (text == null) {
field.setText("");
field.setForeground(originalColor);
} else {
field.setText(text);
field.setForeground(Color.LIGHT_GRAY);
}
field.getDocument().addDocumentListener(this);
setControls();
}
/**
* Creates the filtering controls.
*
* @return See above.
*/
private JPanel createFilteringControl()
{
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
p.add(new JLabel("Filter by"));
String txt = null;
if (TagAnnotationData.class.equals(type)) {
txt = "tag";
} else if (TagAnnotationData.class.equals(type)) {
txt = "attachment";
}
String[] values = new String[2];
StringBuilder builder = new StringBuilder();
builder.append("start of ");
if (txt != null) {
builder.append(txt);
builder.append(" ");
}
builder.append("name");
values[0] = builder.toString();
builder = new StringBuilder();
builder.append("anywhere in ");
if (txt != null) {
builder.append(txt);
builder.append(" ");
}
builder.append("name");
values[1] = builder.toString();
JComboBox box = new JComboBox(values);
int selected = 0;
if (uiDelegate.isFilterAnywhere()) selected = 1;
box.setSelectedIndex(selected);
box.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JComboBox src = (JComboBox) e.getSource();
uiDelegate.setFilterAnywhere(src.getSelectedIndex() == 1);
}
});
JPanel rows = new JPanel();
rows.setLayout(new BoxLayout(rows, BoxLayout.Y_AXIS));
p.add(box);
rows.add(p);
if (!ExperimenterData.class.equals(type)) {
//Filter by owner
p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
p.add(new JLabel("Filter by owner"));
values = new String[3];
values[SelectionWizardUI.ALL] = "All";
values[SelectionWizardUI.CURRENT] = "Owned by me";
values[SelectionWizardUI.OTHERS] = "Owned by others";
box = new JComboBox(values);
box.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JComboBox src = (JComboBox) e.getSource();
uiDelegate.setOwnerIndex(src.getSelectedIndex());
}
});
p.add(box);
rows.add(p);
}
return UIUtilities.buildComponentPanel(rows);
}
/**
* Initializes the components composing the display.
*
* @param userID The id of the user currently logged in.
*/
private void initComponents()
{
addLabel = UIUtilities.setTextFont("");
acceptButton = new JButton("Save");
acceptButton.setToolTipText("Save the selection.");
cancelButton = new JButton("Cancel");
cancelButton.setToolTipText("Cancel the selection.");
resetButton = new JButton("Reset");
resetButton.setToolTipText("Reset the selection.");
addNewButton = new JButton("Add");
addNewButton.setEnabled(false);
addNewButton.setToolTipText("Add to the selection.");
addNewButton.setActionCommand(""+ADD_NEW);
addNewButton.addActionListener(this);
acceptButton.setActionCommand(""+ACCEPT);
acceptButton.addActionListener(this);
acceptButton.setEnabled(false);
resetButton.setEnabled(false);
cancelButton.setActionCommand(""+CANCEL);
cancelButton.addActionListener(this);
resetButton.setActionCommand(""+RESET);
resetButton.addActionListener(this);
//Field creation
addField = new JTextField(10);
addField.setToolTipText("Tag Name");
originalColor = addField.getForeground();
setTextFieldDefault(addField, DEFAULT_TEXT);
addField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e)
{
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
if (addField.isFocusOwner()) {
addNewObjects();
}
}
}
});
descriptionField = new JTextField(15);
descriptionField.setToolTipText("Tag Description");
setTextFieldDefault(descriptionField, DEFAULT_DESCRIPTION);
addField.getDocument().addDocumentListener(this);
addField.addFocusListener(this);
descriptionField.addFocusListener(this);
descriptionField.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e)
{
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
if (descriptionField.isFocusOwner()) {
addNewObjects();
}
}
}
});
}
/** Closes and disposes. */
private void close()
{
setVisible(false);
dispose();
}
/** Closes and disposes. */
private void cancel()
{
close();
firePropertyChange(CANCEL_SELECTION_PROPERTY, Boolean.valueOf(false),
Boolean.valueOf(true));
}
/** Fires a property change with the selected items. */
private void accept()
{
Map<Class<?>, Collection<Object>>
r = new HashMap<Class<?>, Collection<Object>>();
Collection<Object> l = uiDelegate.getSelection();
l.addAll(uiDelegate.getImmutableElements());
r.put(type, l);
firePropertyChange(SELECTED_ITEMS_PROPERTY, null, r);
close();
}
/**
* Builds and lays out the UI.
*
* @param addCreation Pass <code>true</code> to add a component
* allowing creation of object of the passed type,
* <code>false</code> otherwise.
*/
private void buildUI(boolean addCreation)
{
Container c = getContentPane();
c.setLayout(new BorderLayout());
JPanel container = new JPanel();
container.setLayout(new BoxLayout(container, BoxLayout.Y_AXIS));
container.add(uiDelegate);
container.add(createFilteringControl());
if (addCreation && TagAnnotationData.class.equals(type)) {
container.add(createAdditionPane());
}
c.add(container, BorderLayout.CENTER);
c.add(createControlsPane(), BorderLayout.SOUTH);
}
/**
* Builds and lays out the components hosting the controls.
*
* @return See above.
*/
private JPanel createControlsPane()
{
JPanel controlPanel = new JPanel();
controlPanel.setOpaque(false);
controlPanel.setLayout(new FlowLayout());
controlPanel.add(acceptButton);
controlPanel.add(cancelButton);
controlPanel.add(resetButton);
return UIUtilities.buildComponentPanelRight(controlPanel);
}
/**
* Modifies the text of the component displaying the <code>Add</code>
* component.
*/
private void formatAddLabelText()
{
String s = "";
Collection<DataObject> list = uiDelegate.getAvailableSelectedNodes();
if (CollectionUtils.isNotEmpty(list)) {
DataObject data = list.iterator().next();
if (data instanceof TagAnnotationData) {
s = String.format(" in %s Tag set",
((TagAnnotationData) data).getTagValue());
}
}
String tip = String.format(
"Add a new tag%s and select it immediately:", s);
addLabel.setText(tip);
}
/**
* Builds and lays out the component to add new objects to the selection.
*
* @return See above.
*/
private JPanel createAdditionPane()
{
JPanel p = new JPanel();
p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
if (TagAnnotationData.class.equals(type)) {
formatAddLabelText();
p.add(UIUtilities.buildComponentPanel(addLabel));
JPanel pane = new JPanel();
pane.setLayout(new BoxLayout(pane, BoxLayout.X_AXIS));
pane.add(addField);
pane.add(Box.createHorizontalStrut(5));
pane.add(descriptionField);
pane.add(addNewButton);
p.add(pane);
}
return UIUtilities.buildComponentPanel(p);
}
/** Adds new objects to the selection list. */
private void addNewObjects()
{
if (TagAnnotationData.class.equals(type)) {
String text = addField.getText();
if (CommonsLangUtils.isEmpty(text)) return;
String[] names = text.split(SearchUtil.COMMA_SEPARATOR);
List<DataObject> objects = new ArrayList<DataObject>();
String v;
String description = descriptionField.getText();
if (DEFAULT_DESCRIPTION.equals(description)) {
description = null;
}
Set<DataObject> parents = uiDelegate.getAvailableSelectedNodes();
for (int i = 0; i < names.length; i++) {
v = names[i];
TagAnnotationData tag;
if (CommonsLangUtils.isNotBlank(v)) {
tag = new TagAnnotationData(v.trim());
if (description != null) {
tag.setTagDescription(description);
}
if (CollectionUtils.isNotEmpty(parents)) {
tag.setDataObjects(parents);
}
objects.add(tag);
}
}
boolean reset = uiDelegate.addObjects(objects);
if (reset) {
addField.setCaretPosition(0);
setTextFieldDefault(addField, DEFAULT_TEXT);
descriptionField.setCaretPosition(0);
setTextFieldDefault(descriptionField, DEFAULT_DESCRIPTION);
acceptButton.requestFocus();
}
}
}
/**
* Creates a new instance.
*
* @param owner The owner of this dialog.
* @param available The collection of available tags.
* @param type The type of object to handle.
* @param user The current user.
*/
public SelectionWizard(JFrame owner, Collection<Object> available,
Class<?> type, ExperimenterData user)
{
this(owner, available, null, type, user);
}
/**
* Creates a new instance.
*
* @param owner The owner of this dialog.
* @param available The collection of available tags.
* @param type The type of object to handle.
* @param addCreation Pass <code>true</code> to add a component
* allowing creation of object of the passed type,
* <code>false</code> otherwise.
* @param user The current user.
*/
public SelectionWizard(JFrame owner, Collection<Object> available,
Class<?> type, boolean addCreation, ExperimenterData user)
{
this(owner, available, null, type, addCreation, user);
}
/**
* Creates a new instance.
*
* @param owner The owner of this dialog.
* @param available The collection of available items.
* @param selected The collection of selected items.
* @param type The type of object to handle.
* @param user The current user.
*/
public SelectionWizard(JFrame owner, Collection<Object> available,
Collection<Object> selected, Class<?> type,
ExperimenterData user)
{
this(owner, available, selected, type, false, user);
}
/**
* Creates a new instance.
*
* @param owner The owner of this dialog.
* @param available The collection of available items.
* @param selected The collection of selected items.
* @param type The type of object to handle.
* @param addCreation Pass <code>true</code> to add a component
* allowing creation of object of the passed type,
* <code>false</code> otherwise.
* @param user The the current user.
*/
public SelectionWizard(JFrame owner, Collection<Object> available,
Collection<Object> selected, Class<?> type,
boolean addCreation, ExperimenterData user)
{
super(owner);
setModal(true);
uiDelegate = new SelectionWizardUI(this, available, selected, type, user);
uiDelegate.addPropertyChangeListener(this);
this.type = type;
initComponents();
buildUI(addCreation);
setSize(DEFAULT_SIZE);
}
/**
* Sets the collection of nodes that cannot be removed.
*
* @param immutable The collection to set.
*/
public void setImmutableElements(Collection immutable)
{
uiDelegate.setImmutableElements(immutable);
}
/**
* Sets the title, the text and the icon displayed in the header.
*
* @param title The title to set.
* @param text The text to set.
*/
public void setTitle(String title, String text)
{
setTitle(title, text, null);
}
/**
* Sets the title, the text and the icon displayed in the header.
*
* @param title The title to set.
* @param text The text to set.
* @param titleIcon The icon to set.
*/
public void setTitle(String title, String text, Icon titleIcon)
{
setTitle(title);
if (titleIcon == null) {
IconManager icons = IconManager.getInstance();
titleIcon = icons.getIcon(IconManager.WIZARD_48);
}
TitlePanel titlePanel = new TitlePanel(title, text, titleIcon);
getContentPane().add(titlePanel, BorderLayout.NORTH);
}
/**
* Sets the groups.
*
* @param groups The groups to set.
*/
public void setGroups(Collection<GroupData> groups)
{
uiDelegate.setGroups(groups);
}
/**
* Reacts to event fired by the various controls.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent evt)
{
int id = Integer.parseInt(evt.getActionCommand());
switch (id) {
case ACCEPT:
accept();
break;
case CANCEL:
cancel();
break;
case RESET:
uiDelegate.reset();
break;
case ADD_NEW:
addNewObjects();
}
}
/**
* Sets the text of the {@link #acceptButton}.
*
* @param text The value to set.
*/
public void setAcceptButtonText(String text)
{
if (acceptButton != null) acceptButton.setText(text);
}
/**
* Sets the enabled flag of the {@link #addNewButton}.
* @see DocumentListener#insertUpdate(DocumentEvent)
*/
public void insertUpdate(DocumentEvent e) { setControls(); }
/**
* Sets the enabled flag of the {@link #addNewButton}.
* @see DocumentListener#removeUpdate(DocumentEvent)
*/
public void removeUpdate(DocumentEvent e) { setControls(); }
/**
* Sets the controls.
* @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt)
{
String name = evt.getPropertyName();
if (SelectionWizardUI.SELECTION_CHANGE.equals(name)) {
Boolean b = (Boolean) evt.getNewValue();
acceptButton.setEnabled(b.booleanValue());
resetButton.setEnabled(b.booleanValue());
acceptButton.requestFocus();
} else if (SelectionWizardUI.AVAILABLE_SELECTION_CHANGE.equals(name)) {
formatAddLabelText();
}
}
/**
* Resets the values when losing the focus
* @see FocusListener#focusLost(FocusEvent)
*/
public void focusLost(FocusEvent evt) {
Object src = evt.getSource();
if (src == addField) {
String value = addField.getText();
if (CommonsLangUtils.isBlank(value)) {
setTextFieldDefault(addField, DEFAULT_TEXT);
}
} else if (src == descriptionField) {
String value = descriptionField.getText();
if (CommonsLangUtils.isBlank(value)) {
setTextFieldDefault(descriptionField, DEFAULT_DESCRIPTION);
}
}
}
/**
* Resets the values when losing the focus
* @see FocusListener#focusGained(FocusEvent)
*/
public void focusGained(FocusEvent evt)
{
Object src = evt.getSource();
if (src == addField) {
String value = addField.getText();
if (DEFAULT_TEXT.equals(value)) {
addField.setCaretPosition(0);
setTextFieldDefault(addField, null);
}
} else if (src == descriptionField) {
String value = descriptionField.getText();
if (DEFAULT_DESCRIPTION.equals(value)) {
descriptionField.setCaretPosition(0);
setTextFieldDefault(descriptionField, null);
}
}
}
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#changedUpdate(DocumentEvent)
*/
public void changedUpdate(DocumentEvent e) {}
}