diff --git a/datastore_server/.classpath b/datastore_server/.classpath index 8027142a2ecba657b5b1bb0c37b4f0ef5bd1ff71..6f8cb8ff0360c2606619b4b46636be9038132412 100644 --- a/datastore_server/.classpath +++ b/datastore_server/.classpath @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="source/java"/> + <classpathentry kind="src" path="resource"/> <classpathentry kind="src" path="sourceTest/java"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="lib" path="/libraries/jython/standalone/jython.jar" sourcepath="/Users/gpawel/Downloads/jython_installer-2.2.1.jar"/> diff --git a/datastore_server/build/build.xml b/datastore_server/build/build.xml index 1d1e0d2de31f97bf787b0213e4d3bf5194714d09..4a0833783ea88793c152be5c33b1227c05a7f979 100644 --- a/datastore_server/build/build.xml +++ b/datastore_server/build/build.xml @@ -314,6 +314,11 @@ <include name="ch/systemsx/cisd/openbis/dss/generic/shared/api/**/*.class" /> <include name="org/python/core/SyspathArchiveHack.class" /> <include name="${build.info.filename}" /> + </fileset> + <fileset dir="resource"> + <include name="wait.gif" /> + <include name="wrong.png" /> + <include name="ok.png" /> </fileset> <manifest> <attribute name="Main-Class" value="ch.systemsx.cisd.openbis.dss.client.api.gui.DataSetUploadClient" /> diff --git a/datastore_server/resource/ok.png b/datastore_server/resource/ok.png new file mode 100644 index 0000000000000000000000000000000000000000..37f668e270eb93778f13a53f52eff954c1938382 Binary files /dev/null and b/datastore_server/resource/ok.png differ diff --git a/datastore_server/resource/wait.gif b/datastore_server/resource/wait.gif new file mode 100644 index 0000000000000000000000000000000000000000..d0bce1542342e912da81a2c260562df172f30d73 Binary files /dev/null and b/datastore_server/resource/wait.gif differ diff --git a/datastore_server/resource/wrong.png b/datastore_server/resource/wrong.png new file mode 100644 index 0000000000000000000000000000000000000000..0b30e99e6b85076f897e60cad458af39309e87d3 Binary files /dev/null and b/datastore_server/resource/wrong.png differ diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java index cec1a6385112cf3e4929a384a8f72b47a0e37cf5..0a36623eb929eb1ecde9b6174781164692e55f01 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetMetadataPanel.java @@ -21,10 +21,8 @@ import static ch.systemsx.cisd.openbis.dss.client.api.gui.DataSetUploadClient.BU import static ch.systemsx.cisd.openbis.dss.client.api.gui.DataSetUploadClient.LABEL_WIDTH; import java.awt.CardLayout; -import java.awt.Color; import java.awt.Component; import java.awt.Dimension; -import java.awt.Font; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; @@ -40,9 +38,10 @@ import java.util.ArrayList; import java.util.Enumeration; import java.util.HashMap; import java.util.List; +import java.util.concurrent.BlockingDeque; +import java.util.concurrent.LinkedBlockingDeque; import javax.swing.AbstractButton; -import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JButton; import javax.swing.JComboBox; @@ -51,10 +50,7 @@ import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JRadioButton; -import javax.swing.JScrollPane; -import javax.swing.JTextArea; import javax.swing.JTextField; -import javax.swing.ScrollPaneConstants; import ch.systemsx.cisd.openbis.dss.client.api.gui.DataSetUploadClientModel.NewDataSetInfo; import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.NewDataSetDTO.DataSetOwnerType; @@ -67,10 +63,36 @@ import ch.systemsx.cisd.openbis.generic.shared.api.v1.dto.DataSetType; */ public class DataSetMetadataPanel extends JPanel { + private class AsynchronousValidator implements Runnable + { + public void run() + { + while (true) + { + try + { + if (validationQueue.take() != null) + { + // empty the queue + validationQueue.clear(); + + // perform the validation + validateAndNotifyObserversOfChanges(); + } + } catch (Throwable t) + { + // ignore the error, thread cannot die + } + } + } + } + private static final String EMPTY_FILE_SELECTION = ""; private static final long serialVersionUID = 1L; + private final BlockingDeque<Boolean> validationQueue = new LinkedBlockingDeque<Boolean>(); + private final JFrame mainWindow; private final DataSetUploadClientModel clientModel; @@ -98,15 +120,16 @@ public class DataSetMetadataPanel extends JPanel private final HashMap<String, DataSetPropertiesPanel> propertiesPanels = new HashMap<String, DataSetPropertiesPanel>(); - private final JTextArea validationErrors = new JTextArea("Błędy"); - - private final JScrollPane validationErrorsPane = new JScrollPane(validationErrors); + private final ErrorsPanel validationErrors; private NewDataSetInfo newDataSetInfo; public DataSetMetadataPanel(DataSetUploadClientModel clientModel, JFrame mainWindow) { super(); + + new Thread(new AsynchronousValidator()).start(); + setLayout(new GridBagLayout()); // Initialize internal state @@ -131,15 +154,7 @@ public class DataSetMetadataPanel extends JPanel dataSetFileButton = new JButton("Browse..."); dataSetFileLabel = new JLabel("File:", JLabel.TRAILING); - validationErrors.setEditable(false); - validationErrors.setBackground(getBackground()); - validationErrors.setFont(new Font(getFont().getName(), Font.BOLD, getFont().getSize())); - validationErrors.setForeground(Color.RED); - validationErrors.setLineWrap(true); - validationErrors.setWrapStyleWord(true); - validationErrorsPane.setBorder(BorderFactory.createEmptyBorder()); - validationErrorsPane - .setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + validationErrors = new ErrorsPanel(mainWindow); createGui(); } @@ -241,15 +256,18 @@ public class DataSetMetadataPanel extends JPanel } Object selectedItem = e.getItem(); - if (null == selectedItem || EMPTY_FILE_SELECTION == selectedItem) + if (selectedItem != newDataSetInfo.getNewDataSetBuilder().getFile()) { - newDataSetInfo.getNewDataSetBuilder().setFile(null); - } else - { - newDataSetInfo.getNewDataSetBuilder().setFile((File) selectedItem); + if (null == selectedItem || EMPTY_FILE_SELECTION == selectedItem) + { + newDataSetInfo.getNewDataSetBuilder().setFile(null); + } else + { + newDataSetInfo.getNewDataSetBuilder().setFile((File) selectedItem); + } + + validationQueue.add(Boolean.TRUE); } - - validateAndNotifyObserversOfChanges(); } }); @@ -273,7 +291,7 @@ public class DataSetMetadataPanel extends JPanel clientModel.userDidSelectFile(newDirOrNull); updateFileComboBoxList(); updateFileLabel(); - validateAndNotifyObserversOfChanges(); + validationQueue.add(Boolean.TRUE); } } @@ -349,7 +367,8 @@ public class DataSetMetadataPanel extends JPanel createDataSetTypePanel(); addRow(4, dataSetTypePanel); - addRow(5, validationErrorsPane); + + addRow(5, validationErrors); } private void setDataSetType(String dataSetType) @@ -493,8 +512,10 @@ public class DataSetMetadataPanel extends JPanel private void validateAndNotifyObserversOfChanges() { + validationErrors.waitCard(); clientModel.validateNewDataSetInfoAndNotifyObservers(newDataSetInfo); syncErrors(); + validationErrors.showResult(); } private void setOwnerType(DataSetOwnerType type) @@ -505,9 +526,11 @@ public class DataSetMetadataPanel extends JPanel } NewDataSetDTOBuilder builder = newDataSetInfo.getNewDataSetBuilder(); - builder.setDataSetOwnerType(type); - - validateAndNotifyObserversOfChanges(); + if (builder.getDataSetOwnerType() != type) + { + builder.setDataSetOwnerType(type); + validationQueue.add(Boolean.TRUE); + } } protected void setOwnerId(String text) @@ -518,11 +541,15 @@ public class DataSetMetadataPanel extends JPanel } NewDataSetDTOBuilder builder = newDataSetInfo.getNewDataSetBuilder(); - builder.setDataSetOwnerIdentifier(text); - validateAndNotifyObserversOfChanges(); + + if (false == text.equals(builder.getDataSetOwnerIdentifier())) + { + builder.setDataSetOwnerIdentifier(text); + validationQueue.add(Boolean.TRUE); + } } - public void syncErrors() + public synchronized void syncErrors() { // Clear all errors first clearError(ownerIdLabel, ownerIdText, null); @@ -557,7 +584,7 @@ public class DataSetMetadataPanel extends JPanel } } - private void displayError(JLabel label, JComponent component, JTextArea errorAreaOrNull, + private void displayError(JLabel label, JComponent component, ErrorsPanel errorAreaOrNull, ValidationError error) { // Not all errors are applicable to this panel @@ -568,7 +595,7 @@ public class DataSetMetadataPanel extends JPanel UiUtilities.displayError(label, component, errorAreaOrNull, error); } - private void clearError(JLabel label, JComponent component, JTextArea errorAreaOrNull) + private void clearError(JLabel label, JComponent component, ErrorsPanel errorAreaOrNull) { UiUtilities.clearError(label, component, errorAreaOrNull); component.setToolTipText(label.getToolTipText()); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java index cddaa1761f49a1682172bf4dc7bba9d285036e1d..1861daaa54a45c960f161c0ddafa52424f315a0d 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/DataSetUploadClientModel.java @@ -417,7 +417,7 @@ public class DataSetUploadClientModel /** * Validate a new data set info and update the validation errors. */ - public final void validateNewDataSetInfo(NewDataSetInfo newDataSetInfo) + private final void validateNewDataSetInfo(NewDataSetInfo newDataSetInfo) { ArrayList<ValidationError> errors = new ArrayList<ValidationError>(); validateNewDataSetInfo(newDataSetInfo, errors); diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ErrorMessageDialog.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ErrorMessageDialog.java new file mode 100644 index 0000000000000000000000000000000000000000..59073ab15dffb1d835f733e12677b0616c15ea9e --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ErrorMessageDialog.java @@ -0,0 +1,148 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.client.api.gui; + +import java.awt.Point; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; + +import javax.swing.JDialog; +import javax.swing.JFrame; +import javax.swing.JMenuItem; +import javax.swing.JOptionPane; +import javax.swing.JPopupMenu; +import javax.swing.JScrollPane; +import javax.swing.JTextArea; + +/** + * @author Pawel Glyzewski + */ +public class ErrorMessageDialog extends JDialog +{ + + private static final long serialVersionUID = 2915724575615063627L; + + private final JTextArea errorArea = new JTextArea(); + + private final JFrame mainWindow; + + public JPopupMenu popup; + + // An inner class to check whether mouse events are the popup trigger + class MousePopupListener extends MouseAdapter + { + @Override + public void mousePressed(MouseEvent e) + { + checkPopup(e); + } + + @Override + public void mouseClicked(MouseEvent e) + { + checkPopup(e); + } + + @Override + public void mouseReleased(MouseEvent e) + { + checkPopup(e); + } + + private void checkPopup(MouseEvent e) + { + if (e.isPopupTrigger()) + { + popup.show(ErrorMessageDialog.this.errorArea, e.getX(), e.getY()); + } + } + } + + public ErrorMessageDialog(JFrame mainWindow) + { + super(mainWindow, true); + + this.mainWindow = mainWindow; + JScrollPane scrollPane = new JScrollPane(this.errorArea); + Object[] objects = + { "Validation error occured: ", scrollPane }; + + final JOptionPane optionPane = + new JOptionPane(objects, JOptionPane.ERROR_MESSAGE, JOptionPane.DEFAULT_OPTION, + null); + + this.setContentPane(optionPane); + this.errorArea.setEditable(false); + + popup = new JPopupMenu(); + JMenuItem menuItem = new JMenuItem("Copy"); + popup.add(menuItem); + menuItem.addActionListener(new ActionListener() + { + public void actionPerformed(ActionEvent e) + { + errorArea.copy(); + } + }); + + optionPane.addPropertyChangeListener(new PropertyChangeListener() + { + public void propertyChange(PropertyChangeEvent evt) + { + + if (evt.getPropertyName() != null + && evt.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)) + { + + if (evt.getNewValue() != null) + { + if (((Integer) evt.getNewValue()) == JOptionPane.OK_OPTION) + { + optionPane.setValue(optionPane.getInitialValue()); + + ErrorMessageDialog.this.dispose(); + } + } + } + } + + }); + errorArea.addMouseListener(new MousePopupListener()); + } + + public void showErrorMessage(String errorMessage) + { + errorArea.setText(errorMessage); + this.pack(); + + int height = this.getHeight() > 500 ? 500 : this.getHeight(); + int width = this.getWidth() > 600 ? 600 : this.getWidth(); + this.setSize(width, height); + + Point mwLocation = mainWindow.getLocationOnScreen(); + int x = mwLocation.x + (mainWindow.getWidth() / 2) - (this.getWidth() / 2); + int y = mwLocation.y + (mainWindow.getHeight() / 2) - (this.getHeight() / 2); + + this.setLocation(x > 0 ? x : 0, y > 0 ? y : 0); + + this.setVisible(true); + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ErrorsPanel.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ErrorsPanel.java new file mode 100644 index 0000000000000000000000000000000000000000..f22564ac6c96644bbe291c2ad16f06811d90bf51 --- /dev/null +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/ErrorsPanel.java @@ -0,0 +1,171 @@ +/* + * Copyright 2011 ETH Zuerich, CISD + * + * 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 ch.systemsx.cisd.openbis.dss.client.api.gui; + +import java.awt.CardLayout; +import java.awt.Color; +import java.awt.Font; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.BorderFactory; +import javax.swing.ImageIcon; +import javax.swing.JEditorPane; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.ScrollPaneConstants; +import javax.swing.event.HyperlinkEvent; +import javax.swing.event.HyperlinkEvent.EventType; +import javax.swing.event.HyperlinkListener; + +import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationError; + +/** + * @author Pawel Glyzewski + */ +public class ErrorsPanel extends JPanel implements HyperlinkListener +{ + private static final long serialVersionUID = 5932698456919803620L; + + private static final String VALIDATION_ERRORS_CARD = "Errors"; + + private static final String VALIDATION_WAIT_CARD = "Wait"; + + private static final String VALIDATION_SUCCESS_CARD = "Success"; + + private static final String WRONG_ICON_URL = ErrorsPanel.class.getResource("/wrong.png") + .toString(); + + private static final ImageIcon OK_ICON = + new ImageIcon(ErrorsPanel.class.getResource("/ok.png")); + + private static final ImageIcon WAIT_ICON = new ImageIcon( + ErrorsPanel.class.getResource("/wait.gif")); + + private final List<ValidationError> errors = new ArrayList<ValidationError>(); + + private final ErrorMessageDialog errorMessageDialog; + + private final JEditorPane errorsArea; + + private final JScrollPane errorsAreaScroll; + + public ErrorsPanel(JFrame mainWindow) + { + super(new CardLayout()); + + errorMessageDialog = new ErrorMessageDialog(mainWindow); + + errorsArea = new JEditorPane("text/html", ""); + errorsArea.addHyperlinkListener(this); + errorsArea.setEditable(false); + errorsArea.setBackground(getBackground()); + errorsArea.setFont(new Font(getFont().getName(), Font.BOLD, getFont().getSize())); + errorsArea.setForeground(Color.RED); + errorsAreaScroll = new JScrollPane(errorsArea); + errorsAreaScroll.setBorder(BorderFactory.createEmptyBorder()); + errorsAreaScroll + .setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + + this.add(errorsAreaScroll, VALIDATION_ERRORS_CARD); + this.add(new JLabel("Validating, please wait...", WAIT_ICON, JLabel.CENTER), + VALIDATION_WAIT_CARD); + this.add(new JLabel("Validated successfully.", OK_ICON, JLabel.CENTER), + VALIDATION_SUCCESS_CARD); + } + + public void clear() + { + errorsArea.setText(""); + errorsArea.setToolTipText(""); + this.errors.clear(); + } + + public void reportError(ValidationError error) + { + errors.add(error); + displayErrors(); + } + + private void displayErrors() + { + StringBuilder sb = new StringBuilder(); + + int counter = 0; + for (ValidationError error : errors) + { + sb.append( + "<center><div style='align: center' ><a href='http://openbis.ch/" + + (counter++) + + "' style='color: black; text-decoration: none; font-style: normal; vertical-align: middle' ><img border='0' hspace='10' src='") + .append(WRONG_ICON_URL).append("' />") + .append(truncateErrorMessage(error.getErrorMessage())) + .append(" [...]</a></div></center>"); + } + + errorsArea.setText(sb.toString()); + } + + private String truncateErrorMessage(String errorMessage) + { + int truncIndex = 80; + + int indexOfCR = errorMessage.indexOf('\r'); + if (indexOfCR > -1 && indexOfCR < truncIndex) + { + truncIndex = indexOfCR; + } + + int indexOfLF = errorMessage.indexOf('\n'); + if (indexOfLF > -1 && indexOfLF < truncIndex) + { + truncIndex = indexOfLF; + } + + return errorMessage.length() > truncIndex ? errorMessage.substring(0, truncIndex) + : errorMessage; + } + + public void hyperlinkUpdate(HyperlinkEvent event) + { + if (event.getEventType() == EventType.ACTIVATED) + { + URL url = event.getURL(); + int idx = Integer.parseInt(url.getPath().substring(1)); + errorMessageDialog.showErrorMessage(errors.get(idx).getErrorMessage()); + } + } + + public void waitCard() + { + ((CardLayout) this.getLayout()).show(this, VALIDATION_WAIT_CARD); + } + + public void showResult() + { + if (errors.isEmpty()) + { + ((CardLayout) this.getLayout()).show(this, VALIDATION_SUCCESS_CARD); + } else + { + ((CardLayout) this.getLayout()).show(this, VALIDATION_ERRORS_CARD); + } + } +} diff --git a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/UiUtilities.java b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/UiUtilities.java index 988ace56119665b79858058f60764d7941c1d715..620ca3acd849397d2515bc0cb62edca6a985f8cc 100644 --- a/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/UiUtilities.java +++ b/datastore_server/source/java/ch/systemsx/cisd/openbis/dss/client/api/gui/UiUtilities.java @@ -20,7 +20,6 @@ import java.awt.Color; import javax.swing.JComponent; import javax.swing.JLabel; -import javax.swing.JTextArea; import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationError; @@ -30,27 +29,24 @@ import ch.systemsx.cisd.openbis.dss.generic.shared.api.v1.validation.ValidationE public class UiUtilities { - public static void displayError(JLabel label, JComponent component, JTextArea errorAreaOrNull, - ValidationError error) + public static void displayError(JLabel label, JComponent component, + ErrorsPanel errorAreaOrNull, ValidationError error) { component.setToolTipText(error.getErrorMessage()); label.setForeground(Color.RED); if (errorAreaOrNull != null) { - errorAreaOrNull.setText("File validation error: " + error.getErrorMessage()); - errorAreaOrNull.setToolTipText(error.getErrorMessage()); + errorAreaOrNull.reportError(error); } } - public static void clearError(JLabel label, JComponent component, JTextArea errorAreaOrNull) + public static void clearError(JLabel label, JComponent component, ErrorsPanel errorAreaOrNull) { component.setToolTipText(null); label.setForeground(Color.BLACK); if (errorAreaOrNull != null) { - errorAreaOrNull.setText(null); - errorAreaOrNull.setToolTipText(null); + errorAreaOrNull.clear(); } } - }