/** * OrbisGIS is a java GIS application dedicated to research in GIScience. * OrbisGIS is developed by the GIS group of the DECIDE team of the * Lab-STICC CNRS laboratory, see <http://www.lab-sticc.fr/>. * * The GIS group of the DECIDE team is located at : * * Laboratoire Lab-STICC – CNRS UMR 6285 * Equipe DECIDE * UNIVERSITÉ DE BRETAGNE-SUD * Institut Universitaire de Technologie de Vannes * 8, Rue Montaigne - BP 561 56017 Vannes Cedex * * OrbisGIS is distributed under GPL 3 license. * * Copyright (C) 2007-2014 CNRS (IRSTV FR CNRS 2488) * Copyright (C) 2015-2017 CNRS (Lab-STICC UMR CNRS 6285) * * This file is part of OrbisGIS. * * OrbisGIS 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 3 of the License, or (at your option) any later * version. * * OrbisGIS 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 * OrbisGIS. If not, see <http://www.gnu.org/licenses/>. * * For more information, please consult: <http://www.orbisgis.org/> * or contact directly: * info_at_ orbisgis.org */ package org.orbisgis.toolboxeditor.dataui; import net.miginfocom.swing.MigLayout; import net.opengis.wps._2_0.DescriptionType; import net.opengis.wps._2_0.InputDescriptionType; import net.opengis.wps._2_0.OutputDescriptionType; import org.orbisgis.commons.progress.SwingWorkerPM; import org.orbisgis.sif.common.ContainerItem; import org.orbisgis.toolboxeditor.WpsClientImpl; import org.orbisgis.toolboxeditor.utils.ToolBoxIcon; import org.orbiswps.server.model.BoundingBoxData; import org.slf4j.LoggerFactory; import org.xnap.commons.i18n.I18n; import org.xnap.commons.i18n.I18nFactory; import javax.swing.*; import javax.swing.Timer; import javax.swing.event.DocumentListener; import javax.swing.plaf.LayerUI; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import java.awt.*; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.*; import java.beans.EventHandler; import java.beans.PropertyChangeEvent; import java.io.IOException; import java.math.BigInteger; import java.net.URI; import java.util.*; import java.util.List; import java.util.concurrent.ExecutorService; /** * DataUI implementation for BoundingBoxData. * This class generate an interactive UI dedicated to the configuration of a BoundingBoxData. * The interface generated will be used in the ProcessEditor. * * @author Sylvain PALOMINOS **/ public class BoundingBoxDataUI implements DataUI { /** Constant used to pass object as client property throw JComponents **/ private static final String DATA_MAP_PROPERTY = "DATA_MAP_PROPERTY"; private static final String URI_PROPERTY = "URI_PROPERTY"; private static final String BOUNDING_BOX_PROPERTY = "BOUNDING_BOX_PROPERTY"; private static final String IS_OPTIONAL_PROPERTY = "IS_OPTIONAL_PROPERTY"; private static final String INITIAL_DELAY_PROPERTY = "INITIAL_DELAY_PROPERTY"; private static final String TOOLTIP_TEXT_PROPERTY = "TOOLTIP_TEXT_PROPERTY"; private static final String LAYERUI_PROPERTY = "LAYERUI_PROPERTY"; private static final String DEFAULT_ELEMENT_PROPERTY = "DEFAULT_ELEMENT_PROPERTY"; private static final String REFRESH_LIST_LISTENER_PROPERTY = "REFRESH_LIST_LISTENER_PROPERTY"; private static final String COMBOBOX_PROPERTY = "COMBOBOX_PROPERTY"; private static final String TEXT_FIELD_PROPERTY = "TEXT_FIELD_PROPERTY"; /** I18N object */ private static final I18n I18N = I18nFactory.getI18n(BoundingBoxDataUI.class); /** WpsClient using the generated UI. */ private WpsClientImpl wpsClient; @Override public void setWpsClient(WpsClientImpl wpsClient){ this.wpsClient = wpsClient; } @Override public JComponent createUI(DescriptionType inputOrOutput, Map<URI, Object> dataMap, Orientation orientation) { JPanel panel = new JPanel(new MigLayout("fill, ins 0, gap 0")); BoundingBoxData boundingBoxData = null; //Retrieve the JDBCTableFieldValue and if it is optional boolean isOptional = false; if(inputOrOutput instanceof InputDescriptionType){ boundingBoxData = (BoundingBoxData)((InputDescriptionType)inputOrOutput).getDataDescription().getValue(); if(((InputDescriptionType)inputOrOutput).getMinOccurs().equals(new BigInteger("0"))){ isOptional = true; } } else if(inputOrOutput instanceof OutputDescriptionType){ return null; } if(boundingBoxData == null){ return panel; } JTextField textField = new JTextField(); JComboBox<ContainerItem<Object>> comboBox= new JComboBox<>(); //Build an set the text field for the bounding box coordinate textField.setToolTipText(I18N.tr("Enter the coma separated bounding box coordinate : minX,minY,maxX,maxY")); textField.getDocument().putProperty(DATA_MAP_PROPERTY, dataMap); textField.getDocument().putProperty(URI_PROPERTY, URI.create(inputOrOutput.getIdentifier().getValue())); textField.getDocument().putProperty(COMBOBOX_PROPERTY, comboBox); textField.getDocument().putProperty(IS_OPTIONAL_PROPERTY, isOptional); textField.getDocument().addDocumentListener(EventHandler.create(DocumentListener.class, this, "saveDocumentText", "document")); //Build and set the jComboBox containing all the SRID comboBox.setToolTipText(I18N.tr("Select the SRID of the bounding box")); if(boundingBoxData.getDefaultCrs() != null) { ContainerItem<Object> defaultElement = new ContainerItem<Object>(boundingBoxData.getDefaultCrs(), boundingBoxData.getDefaultCrs()); comboBox.putClientProperty(DEFAULT_ELEMENT_PROPERTY, defaultElement); comboBox.addItem(defaultElement); } URI uri = URI.create(inputOrOutput.getIdentifier().getValue()); comboBox.putClientProperty(URI_PROPERTY, uri); comboBox.putClientProperty(BOUNDING_BOX_PROPERTY, boundingBoxData); comboBox.putClientProperty(DATA_MAP_PROPERTY, dataMap); comboBox.putClientProperty(IS_OPTIONAL_PROPERTY, isOptional); if(boundingBoxData.getSupportedCrs().length == 0) { MouseListener refreshListListener = EventHandler.create(MouseListener.class, this, "refreshList", "source", "mouseEntered"); comboBox.putClientProperty(REFRESH_LIST_LISTENER_PROPERTY, refreshListListener); comboBox.addMouseListener(refreshListListener); } else{ for(String crs : boundingBoxData.getSupportedCrs()){ if(! crs.equals(boundingBoxData.getDefaultCrs())) { comboBox.addItem(new ContainerItem<Object>(crs, crs)); } } } comboBox.addMouseListener(EventHandler.create(MouseListener.class, this, "onComboBoxExited", "source", "mouseExited")); comboBox.addItemListener(EventHandler.create(ItemListener.class, this, "onItemSelection", "")); comboBox.setToolTipText(inputOrOutput.getAbstract().get(0).getValue()); //Create the button Browse JButton pasteButton = new JButton(ToolBoxIcon.getIcon(ToolBoxIcon.PASTE)); //"Save" the sourceCA and the JTextField in the button pasteButton.putClientProperty(TEXT_FIELD_PROPERTY, textField); pasteButton.setBorderPainted(false); pasteButton.setContentAreaFilled(false); pasteButton.setToolTipText(I18N.tr("Paste the clipboard")); pasteButton.setMargin(new Insets(0, 0, 0, 0)); //Add the listener for the click on the button pasteButton.addActionListener(EventHandler.create(ActionListener.class, this, "onPaste", "")); //Adds a WaitLayerUI which will be displayed when the toolbox is loading the data WaitLayerUI layerUI = new WaitLayerUI(); JLayer<JComponent> layer = new JLayer<>(comboBox, layerUI); panel.add(textField, "growx"); panel.add(pasteButton, "dock east, growx"); panel.add(layer, "dock east"); comboBox.putClientProperty(LAYERUI_PROPERTY, layerUI); if(boundingBoxData.getDefaultValue() != null){ textField.setText(boundingBoxData.getDefaultValue()); } return panel; } @Override public Map<URI, Object> getDefaultValue(DescriptionType inputOrOutput) { Map<URI, Object> map = new HashMap<>(); BoundingBoxData boundingBoxData = null; if(inputOrOutput instanceof InputDescriptionType){ boundingBoxData = (BoundingBoxData)((InputDescriptionType)inputOrOutput).getDataDescription().getValue(); } else if(inputOrOutput instanceof OutputDescriptionType){ boundingBoxData = (BoundingBoxData)((OutputDescriptionType)inputOrOutput).getDataDescription().getValue(); } if(boundingBoxData.getDefaultCrs() != null) { map.put(URI.create(inputOrOutput.getIdentifier().getValue()), boundingBoxData.getDefaultCrs()); } else{ if(boundingBoxData.getSupportedCrs() != null && boundingBoxData.getSupportedCrs().length != 0) { map.put(URI.create(inputOrOutput.getIdentifier().getValue()), boundingBoxData.getSupportedCrs()[0]); } } return map; } @Override public ImageIcon getIconFromData(DescriptionType inputOrOutput) { return ToolBoxIcon.getIcon(ToolBoxIcon.JDBC_VALUE); } /** * Action do on clicking on the paste button. * @param ae Action event fired. */ public void onPaste(ActionEvent ae){ Object sourceObj = ae.getSource(); if(sourceObj instanceof JButton){ JButton pasteButton = (JButton) sourceObj; JTextField textField = (JTextField) pasteButton.getClientProperty(TEXT_FIELD_PROPERTY); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); //odd: the Object param of getContents is not currently used Transferable contents = clipboard.getContents(null); boolean hasTransferableText = (contents != null) && contents.isDataFlavorSupported(DataFlavor.stringFlavor); if (hasTransferableText) { try { textField.setText((String)contents.getTransferData(DataFlavor.stringFlavor)); } catch (UnsupportedFlavorException | IOException ignored){ } } } } /** * When the jList is exited, reset the tooltipText delay. * @param source JComboBox. */ public void onComboBoxExited(Object source){ //Retrieve the client properties JComboBox<String> comboBox = (JComboBox)source; Object tooltipText = comboBox.getClientProperty(TOOLTIP_TEXT_PROPERTY); if(tooltipText != null) { comboBox.setToolTipText((String)tooltipText); } Object delay = comboBox.getClientProperty(INITIAL_DELAY_PROPERTY); if(delay != null){ ToolTipManager.sharedInstance().setInitialDelay((int)delay); } } /** * Update the JList according to if JDBCTableField parent. * @param source the source JList. */ public void refreshList(Object source){ JComboBox comboBox = (JComboBox)source; //Instantiate a worker which will do the list update in a separeted swing thread FieldValueWorker worker = new FieldValueWorker(comboBox); ExecutorService executorService = wpsClient.getExecutorService(); if(executorService != null){ executorService.execute(worker); } else{ worker.execute(); } MouseListener listener = (MouseListener)comboBox.getClientProperty(REFRESH_LIST_LISTENER_PROPERTY); comboBox.removeMouseListener(listener); } /** * Action done on selecting an item. * @param event Item event fired. */ public void onItemSelection(ItemEvent event){ JComboBox comboBox = (JComboBox)event.getSource(); URI uri = (URI)comboBox.getClientProperty(URI_PROPERTY); HashMap<URI, Object> dataMap = (HashMap<URI, Object>)comboBox.getClientProperty(DATA_MAP_PROPERTY); dataMap.put(uri, event.getItem()); } /** * SwingWorker doing the list update in a separated Swing Thread */ private class FieldValueWorker extends SwingWorkerPM{ private JComboBox<ContainerItem<Object>> comboBox; public FieldValueWorker(JComboBox<ContainerItem<Object>> comboBox){ this.comboBox = comboBox; } @Override protected Object doInBackground() throws Exception { WaitLayerUI layerUI = (WaitLayerUI)comboBox.getClientProperty(LAYERUI_PROPERTY); Object defaultElementObject = comboBox.getClientProperty(DEFAULT_ELEMENT_PROPERTY); ContainerItem<Object> defaultElement = null; if(defaultElementObject != null){ defaultElement = (ContainerItem<Object>)defaultElementObject; } this.setTaskName(I18N.tr("Updating the SRID values")); layerUI.start(); comboBox.removeAllItems(); List<String> listFields = wpsClient.getSRIDList(); Collections.sort(listFields); int index = 0; boolean selectedFound = false; for (String field : listFields) { if(defaultElement != null && field.equals(defaultElement.getLabel())){ selectedFound = true; } comboBox.addItem(new ContainerItem<Object>(field, field)); if(!selectedFound) { index++; } } layerUI.stop(); //If the jList doesn't contains any values or contains only the default item, // it mean that the JDBCTableField hasn't been well selected. //So show a tooltip text to warn the user. if( comboBox.getItemCount() == 0 || (comboBox.getItemCount() == 1 && (comboBox.getItemAt(0).equals(defaultElement))) ) { comboBox.putClientProperty(INITIAL_DELAY_PROPERTY, ToolTipManager.sharedInstance().getInitialDelay()); comboBox.putClientProperty(TOOLTIP_TEXT_PROPERTY, comboBox.getToolTipText()); ToolTipManager.sharedInstance().setInitialDelay(0); ToolTipManager.sharedInstance().setDismissDelay(2500); comboBox.setToolTipText(I18N.tr("No SRID found.")); ToolTipManager.sharedInstance().mouseMoved( new MouseEvent(comboBox,MouseEvent.MOUSE_MOVED,System.currentTimeMillis(),0,0,0,0,false)); } comboBox.setSelectedIndex(index); comboBox.repaint(); return null; } } /** * Saves the bounding box value set in the text field. * @param document Document of the JTextField */ public void saveDocumentText(Document document){ try { Map<URI, Object> dataMap = (Map<URI, Object>)document.getProperty(DATA_MAP_PROPERTY); URI uri = (URI)document.getProperty(URI_PROPERTY); JComboBox<ContainerItem<String>> comboBox = (JComboBox<ContainerItem<String>>) document.getProperty(COMBOBOX_PROPERTY); String text = comboBox.getSelectedItem().toString() + ";" + document.getText(0, document.getLength()); dataMap.put(uri, text); } catch (BadLocationException e) { LoggerFactory.getLogger(BoundingBoxData.class).error(e.getMessage()); } } /** * Wait layer displayed on doing an update on the list. */ private class WaitLayerUI extends LayerUI<JComponent> implements ActionListener { /** Indicates if the layer is running. */ private boolean mIsRunning; /** Indicates if the layer is fading. */ private boolean mIsFadingOut; /** Timer used to refresh the layer. */ private Timer mTimer; /** Angle of the drawn spinner. */ private int mAngle; /** Fade count used to fade the spinner. */ private int mFadeCount; /** Maximum value of the fade count. */ private int mFadeLimit = 15; @Override public void paint(Graphics g, JComponent c) { int w = c.getWidth(); int h = c.getHeight(); // Paint the view. super.paint (g, c); if (!mIsRunning) { return; } Graphics2D g2 = (Graphics2D)g.create(); float fade = (float)mFadeCount / (float)mFadeLimit; // Gray it out. Composite urComposite = g2.getComposite(); g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, .5f * fade)); g2.fillRect(0, 0, w, h); g2.setComposite(urComposite); // Paint the wait indicator. int s = Math.min(w, h) / 5; int cx = w / 2; int cy = h / 2; g2.setPaint(Color.white); //"Loading source" painting Font font = g2.getFont().deriveFont(Font.PLAIN, s / 3); g2.setFont(font); FontMetrics metrics = g2.getFontMetrics(font); int w1 = metrics.stringWidth(I18N.tr("Loading")); int w2 = metrics.stringWidth(I18N.tr("fields")); int h1 = metrics.getHeight(); g2.drawString(I18N.tr("Loading"), cx - w1 / 2, cy - h1 / 2); g2.drawString(I18N.tr("source"), cx - w2 / 2, cy + h1 / 2); //waiter painting g2.setStroke(new BasicStroke(s / 4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND)); g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2.rotate(Math.PI * mAngle / 180, cx, cy); for (int i = 1; i < 12; i++) { float scale = (11.0f - (float)i) / 11.0f; g2.drawLine(cx + s, cy, cx + s * 2, cy); g2.rotate(-Math.PI / 6, cx, cy); g2.setComposite(AlphaComposite.getInstance( AlphaComposite.SRC_OVER, scale * fade)); } Toolkit.getDefaultToolkit().sync(); g2.dispose(); } /** * Method called each time the refresh timer is woken up. * @param e Unused. */ public void actionPerformed(ActionEvent e) { if (mIsRunning) { firePropertyChange("tick", 0, 1); mAngle += 3; if (mAngle >= 360) { mAngle = 0; } if (mIsFadingOut) { if (--mFadeCount <= 0) { mIsRunning = false; mTimer.stop(); } } else if (mFadeCount < mFadeLimit) { mFadeCount++; } } } /** * Starts the layer an launch the drawing. */ public void start() { if (mIsRunning) { return; } // Run a thread for animation. mIsRunning = true; mIsFadingOut = false; mFadeCount = 0; int fps = 24; int tick = 1000 / fps; mTimer = new Timer(tick, this); mTimer.start(); } /** * Stops the layer an stops the drawing. */ public void stop() { mIsFadingOut = true; } @Override public void applyPropertyChange(PropertyChangeEvent pce, JLayer l) { if ("tick".equals(pce.getPropertyName())) { l.repaint(); } } } }