/*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2014 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.dataBrowser.util;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSeparator;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import omero.gateway.model.SearchParameters;
import org.jdesktop.swingx.JXDatePicker;
import org.openmicroscopy.shoola.agents.dataBrowser.DataBrowserAgent;
import org.openmicroscopy.shoola.agents.dataBrowser.IconManager;
import org.openmicroscopy.shoola.agents.dataBrowser.view.SearchPanel;
import org.openmicroscopy.shoola.agents.metadata.MetadataViewerAgent;
import org.openmicroscopy.shoola.agents.util.EditorUtil;
import org.openmicroscopy.shoola.agents.util.SelectionWizard;
import org.openmicroscopy.shoola.agents.util.tagging.util.TagCellRenderer;
import org.openmicroscopy.shoola.agents.util.tagging.util.TagItem;
import org.openmicroscopy.shoola.env.data.util.FilterContext;
import org.openmicroscopy.shoola.util.ui.HistoryDialog;
import org.openmicroscopy.shoola.util.ui.RatingComponent;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.search.SearchUtil;
import omero.gateway.model.DataObject;
import omero.gateway.model.TagAnnotationData;
import omero.gateway.model.TextualAnnotationData;
/**
* Modal dialog used to filter data.
*
* @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
* <small>
* (<b>Internal version:</b> $Revision: $Date: $)
* </small>
* @since OME3.0
*/
public class FilteringDialog
extends JDialog
implements ActionListener, DocumentListener, PropertyChangeListener
{
/** Bound property indicating to load the existing tags. */
public static final String LOAD_TAG_PROPERTY = "loadTag";
/** Bound property indicating to filter the data. */
public static final String FILTER_PROPERTY = "filter";
/** The title of the dialog. */
private static final String TITLE = "Filtering elements in Workspace";
/** Text indicating how to use the tag entries. */
private static final String DESCRIPTION = "Separate tags with " +
""+SearchUtil.COMMA_SEPARATOR;
/** Action id indicating to close the dialog. */
private static final int CANCEL = 0;
/** Action id indicating to filter the data. */
private static final int FILTER = 1;
/** Action id indicating to load the tags. */
private static final int LOAD_TAGS = 2;
/** ID to filter by 'greater or equal than' comparison. */
private static final int GREATER_EQUAL = 0;
/** ID to filter by 'lower or equal than' comparison. */
private static final int LOWER_EQUAL = 1;
/** ID to filter by the exact match. */
private static final int EQUAL = 2;
/** The maximum number of options. */
private static final int MAX = 2;
/** Holds the different comparison options: {@link #GREATER_EQUAL}, {@link #LOWER_EQUAL} and {@link #EQUAL} */
private static final String[] COMPARISON_OPTIONS;
static {
COMPARISON_OPTIONS = new String[MAX+1];
COMPARISON_OPTIONS[GREATER_EQUAL] = "greater or equal to";
COMPARISON_OPTIONS[LOWER_EQUAL] = "lower or equal to";
COMPARISON_OPTIONS[EQUAL] = "equal to";
}
/** The component to select the rating. */
private JCheckBox ratingBox;
/** The component to select the comments. */
private JCheckBox commentsBox;
/** The component to select the name. */
private JCheckBox nameBox;
/** The component to select the tags. */
private JCheckBox tagsBox;
/** The component to select a time interval. */
private JComboBox timeOptions;
/** The component to select the number of ROIs. */
private JCheckBox roiBox;
/** Used to select the rating comparison option. */
private JComboBox ratingOptions;
/** Used to select the ROI number comparison option. */
private JComboBox roiOptions;
/** Date used to specify the beginning of the time interval. */
private JXDatePicker fromDate;
/** Date used to specify the ending of the time interval. */
private JXDatePicker toDate;
/** The rating component. */
private RatingComponent rating;
/** Spinner to select the number of ROIs */
private JSpinner roiSpinner;
/** The field area to collect the tags. */
private JTextField tagsArea;
/** The field area to collect the comments. */
private JTextField commentsArea;
/** The field area to collect the name. */
private JTextField nameArea;
/** Button to close the dialog. */
private JButton cancelButton;
/** Button to filter the data. */
private JButton filterButton;
/** Button to load the existing tags. */
private JButton loadTagsButton;
/** The dialog displaying the existing tags. */
private HistoryDialog tagsDialog;
/** The collection of existing tags. */
private Collection existingTags;
/**
* Flag indicating that the tags loading was happening using the
* code completion mechanism.
*/
private boolean codeCompletion;
/** Displays the available tags and allows the users to select them. */
private void showTagsWizard()
{
IconManager icons = IconManager.getInstance();
String title = "Filter By Tags";
String text = "Select the Tags to filter by.";
Collection selected = new ArrayList<TagAnnotationData>();
Iterator i = existingTags.iterator();
TagAnnotationData tag;
List<String> l = SearchUtil.splitTerms(tagsArea.getText(),
SearchUtil.COMMA_SEPARATOR);
Collection available = new ArrayList<TagAnnotationData>();
while (i.hasNext()) {
tag = (TagAnnotationData) i.next();
if (l.contains(tag.getTagValue()))
selected.add(tag);
else available.add(tag);
}
SelectionWizard wizard = new SelectionWizard(
DataBrowserAgent.getRegistry().getTaskBar().getFrame(),
available, selected, TagAnnotationData.class, false,
DataBrowserAgent.getUserDetails());
wizard.setTitle(title, text, icons.getIcon(IconManager.TAG_FILTER_48));
wizard.addPropertyChangeListener(this);
UIUtilities.centerAndShow(wizard);
}
/**
* Handles the selection.
*
* @param tags The selected tags.
*/
private void handleTagsSelection(Collection tags)
{
if (tags == null || tags.size() == 0) return;
tagsArea.getDocument().removeDocumentListener(this);
tagsArea.setText("");
tagsArea.getDocument().addDocumentListener(this);
Iterator i = tags.iterator();
TagAnnotationData tag;
while (i.hasNext()) {
tag = (TagAnnotationData) i.next();
enterTag(tag, false);
}
}
/**
* Enters the tag.
*
* @param tag The tag value to enter.
* @param removeLast Pass <code>true</code> to remove the last item,
* <code>false</code> otherwise.
*/
private void enterTag(TagAnnotationData tag, boolean removeLast)
{
String text = tag.getTagValue();
List<String> l = SearchUtil.splitTerms(tagsArea.getText(),
SearchUtil.COMMA_SEPARATOR);
if (removeLast && l.size() > 0) l.remove(l.size()-1);
String result = SearchUtil.formatString(text, l);
tagsArea.getDocument().removeDocumentListener(this);
tagsArea.setText(result);
tagsArea.getDocument().addDocumentListener(this);
}
/** Loads the tags and adds code completion. */
private void handleTagInsert()
{
codeCompletion = true;
if (existingTags == null) {
firePropertyChange(LOAD_TAG_PROPERTY, Boolean.valueOf(false),
Boolean.valueOf(true));
return;
}
codeCompletion();
if (tagsDialog == null) return;
String name = tagsArea.getText();
List<String> l = SearchUtil.splitTerms(name,
SearchUtil.COMMA_SEPARATOR);
if (l.size() > 0) {
if (tagsDialog.setSelectedTextValue(l.get(l.size()-1).trim())) {
Rectangle r = tagsArea.getBounds();
tagsDialog.show(tagsArea, 0, r.height);
tagsArea.requestFocus();
} else tagsDialog.setVisible(false);
}
}
/** Initializes the {@link HistoryDialog} used for code completion. */
private void codeCompletion()
{
if (tagsDialog != null) return;
Rectangle r = tagsArea.getBounds();
Object[] data = null;
if (existingTags != null && existingTags.size() > 0) {
data = new Object[existingTags.size()];
Iterator j = existingTags.iterator();
DataObject object;
TagItem item;
int i = 0;
while (j.hasNext()) {
object = (DataObject) j.next();
item = new TagItem(object);
data[i] = item;
i++;
}
long id = MetadataViewerAgent.getUserDetails().getId();
tagsDialog = new HistoryDialog(data, r.width);
tagsDialog.setListCellRenderer(new TagCellRenderer(id));
tagsDialog.addPropertyChangeListener(
HistoryDialog.SELECTION_PROPERTY, this);
}
}
/** Sets the properties of the dialog. */
private void setProperties()
{
setTitle(TITLE);
setModal(true);
}
/** Handles text entered in the tagging area. */
private void handleEnter()
{
if (tagsDialog == null || !tagsDialog.isVisible())
return;
String name = tagsArea.getText();
if (name == null) return;
TagItem o = (TagItem) tagsDialog.getSelectedTextValue();
if (o == null) return;
DataObject ho = o.getDataObject();
if (ho instanceof TagAnnotationData) {
enterTag((TagAnnotationData) ho, true);
}
}
/** Initializes the components. */
private void initComponents()
{
ratingBox = new JCheckBox("Rating:");
timeOptions = new JComboBox();
timeOptions.setToolTipText(SearchPanel.DATE_TYPE_TOOLTIP);
timeOptions.addItem(SearchPanel.ITEM_IMPORTDATE);
timeOptions.addItem(SearchPanel.ITEM_ACQUISITIONDATE);
commentsBox = new JCheckBox("Comments");
nameBox = new JCheckBox("Name");
tagsBox = new JCheckBox("Tags");
tagsBox.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
loadTagsButton.setEnabled(tagsBox.isSelected());
}
});
roiBox = new JCheckBox("ROIs");
ratingOptions = new JComboBox(COMPARISON_OPTIONS);
roiOptions = new JComboBox(COMPARISON_OPTIONS);
rating = new RatingComponent(5, RatingComponent.HIGH_SIZE);
roiSpinner = new JSpinner(new SpinnerNumberModel(1, 0, Integer.MAX_VALUE, 1));
fromDate = UIUtilities.createDatePicker(false, EditorUtil.DATE_PICKER_FORMAT);
toDate = UIUtilities.createDatePicker(false, EditorUtil.DATE_PICKER_FORMAT);
IconManager icons = IconManager.getInstance();
loadTagsButton = new JButton(icons.getIcon(IconManager.TAG_FILTER));
UIUtilities.unifiedButtonLookAndFeel(loadTagsButton);
loadTagsButton.setToolTipText("Load existing Tags for filtering.");
loadTagsButton.setEnabled(false);
loadTagsButton.setActionCommand(""+LOAD_TAGS);
loadTagsButton.addActionListener(this);
tagsArea = new JTextField();
tagsArea.setColumns(15);
tagsArea.getDocument().addDocumentListener(this);
tagsArea.addKeyListener(new KeyAdapter() {
/** Finds the phrase. */
public void keyPressed(KeyEvent e)
{
Object source = e.getSource();
if (source != tagsArea) return;
switch (e.getKeyCode()) {
case KeyEvent.VK_ENTER:
handleEnter();
break;
case KeyEvent.VK_UP:
if (tagsDialog != null && tagsDialog.isVisible())
tagsDialog.setSelectedIndex(false);
break;
case KeyEvent.VK_DOWN:
if (tagsDialog != null && tagsDialog.isVisible())
tagsDialog.setSelectedIndex(true);
}
}
});
commentsArea = new JTextField();
commentsArea.setColumns(15);
nameArea = new JTextField();
nameArea.setColumns(15);
cancelButton = new JButton("Cancel");
cancelButton.setToolTipText("Close.");
cancelButton.setActionCommand(""+CANCEL);
cancelButton.addActionListener(this);
filterButton = new JButton("Filter");
filterButton.setToolTipText("Filter the data.");
filterButton.setActionCommand(""+FILTER);
filterButton.addActionListener(this);
//getRootPane().setDefaultButton(filterButton);
}
/** Organizes the filtering parameters. */
private void filter()
{
FilterContext context = new FilterContext();
if (ratingBox.isSelected()) {
int index = -1;
switch (ratingOptions.getSelectedIndex()) {
case GREATER_EQUAL:
index = FilterContext.GREATER_EQUAL;
break;
case LOWER_EQUAL:
index = FilterContext.LOWER_EQUAL;
break;
case EQUAL:
index = FilterContext.EQUAL;
};
context.setRate(index, rating.getCurrentValue());
}
if (roiBox.isSelected()) {
int index = -1;
switch (roiOptions.getSelectedIndex()) {
case GREATER_EQUAL:
index = FilterContext.GREATER_EQUAL;
break;
case LOWER_EQUAL:
index = FilterContext.LOWER_EQUAL;
break;
case EQUAL:
index = FilterContext.EQUAL;
};
context.setRois(index, ((Number)roiSpinner.getValue()).intValue());
}
Date d = fromDate.getDate();
Timestamp start = null;
if (d != null) start = new Timestamp(d.getTime());
Timestamp end = null;
d = toDate.getDate();
if (d != null) end = new Timestamp(d.getTime());
context.setTimeInterval(start, end);
context.setTimeType(timeOptions.getSelectedItem().equals(
SearchPanel.ITEM_ACQUISITIONDATE) ?
SearchParameters.DATE_ACQUISITION :
SearchParameters.DATE_IMPORT);
if (tagsBox.isSelected()) {
List<String> l = SearchUtil.splitTerms(tagsArea.getText(),
SearchUtil.COMMA_SEPARATOR);
if (l != null && l.size() > 0) {
context.addAnnotationType(TagAnnotationData.class, l);
}
}
if (commentsBox.isSelected()) {
List<String> l = SearchUtil.splitTerms(commentsArea.getText(),
SearchUtil.COMMA_SEPARATOR);
if (l != null && l.size() > 0) {
context.addAnnotationType(TextualAnnotationData.class, l);
}
}
if (nameBox.isSelected()) {
List<String> l = SearchUtil.splitTerms(nameArea.getText(),
SearchUtil.COMMA_SEPARATOR);
if (l != null && l.size() > 0) {
context.addName(l);
}
}
firePropertyChange(FILTER_PROPERTY, null, context);
setVisible(false);
}
/**
* Builds and lays out the components used to select the number of ROIs.
*
* @return See above.
*/
private JPanel buildRoiPane() {
JPanel p = new JPanel();
p.add(roiBox);
p.add(roiOptions);
p.add(roiSpinner);
return UIUtilities.buildComponentPanel(p, 0, 0);
}
/**
* Builds and lays out the components used to select the rating level.
*
* @return See above.
*/
private JPanel buildRatingPane()
{
JPanel p = new JPanel();
p.add(ratingBox);
p.add(ratingOptions);
p.add(rating);
return UIUtilities.buildComponentPanel(p, 0, 0);
}
/**
* Builds and lays out the components used to select a time range.
*
* @return See above.
*/
private JPanel buildCalendarPane()
{
JPanel date = new JPanel();
date.add(UIUtilities.setTextFont("From: "));
date.add(fromDate);
date.add(UIUtilities.setTextFont("To: "));
date.add(toDate);
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS));
p.add(timeOptions);
p.add(date);
return UIUtilities.buildComponentPanel(p, 0, 0);
}
/**
* Builds and lays out the components used to select the tags.
*
* @return See above.
*/
public JPanel buildTagsPane()
{
JPanel p = new JPanel();
p.setLayout(new GridBagLayout());
GridBagConstraints c = new GridBagConstraints();
c.anchor = GridBagConstraints.FIRST_LINE_START;
c.fill = GridBagConstraints.HORIZONTAL;
c.gridx = 0;
c.gridy = 0;
p.add(tagsBox, c);
c.gridx++;
p.add(loadTagsButton, c);
c.gridx++;
p.add(Box.createHorizontalStrut(5), c);
c.gridx++;
c.weightx = 0.5;
p.add(tagsArea, c);
c.gridy++;
p.add(UIUtilities.setTextFont(DESCRIPTION, Font.ITALIC, 10), c);
//p.add(tagsBox);
//p.add(tagsArea);
return UIUtilities.buildComponentPanel(p, 5, 5);
}
/**
* Builds and lays out the components used to select the comments.
*
* @return See above.
*/
public JPanel buildCommentsPane()
{
JPanel p = new JPanel();
p.add(commentsBox);
p.add(commentsArea);
return UIUtilities.buildComponentPanel(p, 0, 0);
}
/**
* Builds and lays out the components used to select the name.
*
* @return See above.
*/
public JPanel buildNamePane()
{
JPanel p = new JPanel();
p.add(nameBox);
p.add(nameArea);
return UIUtilities.buildComponentPanel(p, 0, 0);
}
/**
* Builds the selection component.
*
* @return See above.
*/
private JPanel buildSelectionPane()
{
JPanel p = new JPanel();
p.setLayout(new BoxLayout(p, BoxLayout.Y_AXIS));
p.add(buildTagsPane());
p.add(new JSeparator(JSeparator.HORIZONTAL));
p.add(buildNamePane());
p.add(new JSeparator(JSeparator.HORIZONTAL));
p.add(buildCommentsPane());
p.add(new JSeparator(JSeparator.HORIZONTAL));
p.add(buildRatingPane());
p.add(new JSeparator(JSeparator.HORIZONTAL));
p.add(buildRoiPane());
p.add(new JSeparator(JSeparator.HORIZONTAL));
p.add(buildCalendarPane());
p.add(new JSeparator(JSeparator.HORIZONTAL));
return p;
}
/**
* Builds and lays out the tool bar.
*
* @return See above.
*/
private JPanel buildToolBar()
{
JPanel p = new JPanel();
p.add(filterButton);
p.add(Box.createHorizontalStrut(5));
p.add(cancelButton);
return UIUtilities.buildComponentPanelRight(p);
}
/** Builds and lays out the UI. */
private void buildGUI()
{
Container c = getContentPane();
c.add(buildSelectionPane(), BorderLayout.CENTER);
c.add(buildToolBar(), BorderLayout.SOUTH);
}
/**
* Creates a new instance.
*
* @param owner The owner of the frma.ce
*/
public FilteringDialog(JFrame owner)
{
super(owner);
setProperties();
initComponents();
buildGUI();
pack();
}
/**
* Sets the text of the {@link #tagsArea}.
*
* @param text The value to set.
*/
public void setTagsText(String text)
{
if (text == null) return;
text = text.trim();
tagsArea.getDocument().removeDocumentListener(this);
tagsArea.setText(text);
tagsArea.getDocument().addDocumentListener(this);
tagsBox.setSelected(true);
}
/**
* Sets the text of the {@link #commentsArea}.
*
* @param text The value to set.
*/
public void setCommentsText(String text)
{
if (text == null) return;
text = text.trim();
commentsArea.setText(text);
commentsBox.setSelected(true);
}
/**
* Sets the value of the {@link #rating} component.
*
* @param value The value to set.
*/
public void setRatingLevel(int value)
{
rating.setValue(value);
ratingBox.setSelected(true);
}
/**
* Set the state of the dialog to reflect
* filtering for images with ROIs
*/
public void setHasROIs() {
roiSpinner.setValue(1);
roiBox.setSelected(true);
roiOptions.setSelectedIndex(GREATER_EQUAL);
}
/**
* Set the state of the dialog to reflect
* filtering for images without ROIs
*/
public void setNoROIs() {
roiSpinner.setValue(0);
roiBox.setSelected(true);
roiOptions.setSelectedIndex(EQUAL);
}
/**
* Unselects all checkboxes
*/
public void unselectAll() {
commentsBox.setSelected(false);
nameBox.setSelected(false);
ratingBox.setSelected(false);
roiBox.setSelected(false);
tagsBox.setSelected(false);
}
/**
* Sets the collection of tags and brings up the completion dialog
* if any.
*
* @param tags The value to set.
* @param notify Pass <code>true</code> to display the code completion
* dialog or the selection wizard.
*/
public void setTags(Collection tags, boolean notify)
{
if (tags == null) return;
existingTags = tags;
if (notify) {
if (codeCompletion) codeCompletion();
else showTagsWizard();
}
}
/**
* Reacts to various controls.
* @see ActionListener#actionPerformed(ActionEvent)
*/
public void actionPerformed(ActionEvent e)
{
int index = Integer.parseInt(e.getActionCommand());
switch (index) {
case CANCEL:
setVisible(false);
break;
case FILTER:
filter();
break;
case LOAD_TAGS:
if (existingTags != null)
showTagsWizard();
else {
codeCompletion = false;
firePropertyChange(LOAD_TAG_PROPERTY,
Boolean.valueOf(false), Boolean.valueOf(true));
}
}
}
/**
* Sets the tag value.
* @see PropertyChangeListener#propertyChange(PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent evt)
{
String name = evt.getPropertyName();
if (HistoryDialog.SELECTION_PROPERTY.equals(name)) {
Object item = evt.getNewValue();
if (!(item instanceof TagItem)) return;
DataObject ho = ((TagItem) item).getDataObject();
if (ho instanceof TagAnnotationData) {
enterTag((TagAnnotationData) ho, true);
}
} else if (SelectionWizard.SELECTED_ITEMS_PROPERTY.equals(name)) {
Map m = (Map) evt.getNewValue();
if (m == null || m.size() != 1) return;
Set set = m.entrySet();
Entry entry;
Iterator i = set.iterator();
Class type;
while (i.hasNext()) {
entry = (Entry) i.next();
type = (Class) entry.getKey();
handleTagsSelection(
(Collection) entry.getValue());
}
}
}
/**
* Fires property indicating that some text has been entered.
* @see DocumentListener#insertUpdate(DocumentEvent)
*/
public void insertUpdate(DocumentEvent e)
{
handleTagInsert();
}
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#removeUpdate(DocumentEvent)
*/
public void removeUpdate(DocumentEvent e) {}
/**
* Required by the {@link DocumentListener} I/F but no-op implementation
* in our case.
* @see DocumentListener#changedUpdate(DocumentEvent)
*/
public void changedUpdate(DocumentEvent e) {}
}