/*
* Constellation - An open source and standard compliant SDI
* http://www.constellation-sdi.org
*
* Copyright 2014 Geomatys.
*
* 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 org.constellation.gui;
// Interface utilisateur
import org.constellation.catalog.ConfigurationKey;
import org.constellation.catalog.Database;
import org.constellation.resources.i18n.ResourceKeys;
import org.constellation.resources.i18n.Resources;
import org.geotoolkit.util.Utilities;
import org.geotools.resources.SwingUtilities;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
// Modèles et événements
// Divers
// Geotools dependencies
// Constellation
/**
* Editeur de la configuration de l'application. La configuration comprend entre autres les
* instructions SQL à utiliser pour accéder à la base de données. Cet objet peut être construit
* en lui spécifiant en paramètres l'objet {@link Database} qui contient les instructions SQL à
* utiliser. On peut ensuite appeler {@link #addKey} pour ajouter un aspect de la configuration
* qui poura être édité. Enfin, on peut appeler la méthode {@link #showDialog} pour faire apparaître
* l'éditeur.
*
* @author Martin Desruisseaux
* @version $Id$
*/
public class ConfigurationEditor extends JPanel {
/**
* Pour compatibilités entre les enregistrements binaires de différentes versions.
*/
private static final long serialVersionUID = -7936405915390502830L;
/**
* Liste des clés représentant les instructions SQL éditables.
*/
private final List<ConfigurationKey> keySQL = new ArrayList<ConfigurationKey>();
/**
* Liste des instructions SQL éditées par l'utilisateur.
*/
private final List<String> userSQL = new ArrayList<String>();
/**
* Base de données à éditer.
*/
protected final Database configuration;
/**
* Journal dans lequel écrire une notification
* des requêtes qui ont été changées.
*/
private final Logger logger;
/**
* Composante dans laquelle l'utilisateur pourra éditer les instructions SQL.
* Avant de changer la requête à éditer, le contenu de ce champ devra être
* copié dans {@code userSQL.get(index)}.
*/
private final JTextArea valueArea = new JTextArea(5, 40);
/**
* Modèle pour l'affichage de la liste des noms descriptifs des instructions SQL.
* Ce modèle s'occupe des transferts entre {@code valueArea} et {@code userSQL}.
*/
private final Model model = new Model();
/**
* Liste des instructions SQL.
*/
private final JList sqlList = new JList(model);
/**
* Modèle pour l'affichage de la liste des noms descriptifs des instructions SQL.
*
* @version $Id$
* @author Martin Desruisseaux
*/
private final class Model extends AbstractListModel implements ListSelectionListener, ActionListener {
/**
* Pour compatibilités entre les enregistrements binaires de différentes versions.
*/
private static final long serialVersionUID = 5243424642395410933L;
/**
* Index de l'instruction sélectionné.
*/
int index = -1;
/**
* Taille qu'avait {@link #userSQL} lors du dernier appel de {@link #update}.
*/
private int lastSize;
/**
* Retourne le nombre d'instructions.
*/
public int getSize() {
return keySQL.size();
}
/**
* Retourne l'instruction à l'index spécifié.
*/
public Object getElementAt(final int index) {
return keySQL.get(index).getKey();
}
/**
* Sélectionne une nouvelle instruction. Le
* contenu du champ de texte sera mis à jour.
*/
public void valueChanged(final ListSelectionEvent event) {
if (index>=0 && index<userSQL.size()) {
commit();
}
valueTextChanged();
}
/**
* Sauvegarde la requête SQL que l'utilisateur vient de modifier.
* Cette modification n'est pas encore enregistrées dans les
* configuration. Cette étape sera faite à la fin par la méthode
* {@link #save()} si l'utilisateur clique sur "Ok"
*/
final void commit() {
String editedText = valueArea.getText();
if (editedText.trim().length() == 0) {
editedText = keySQL.get(index).getDefaultValue();
}
userSQL.set(index, editedText);
}
/**
* Affiche dans {@code valueArea} l'instruction SQL qui
* correspond à la sélection courrante de l'utilisateur.
*/
void valueTextChanged() {
index = sqlList.getSelectedIndex();
if (index>=0 && index<userSQL.size()) {
valueArea.setText(userSQL.get(index));
valueArea.setEnabled(true);
} else {
valueArea.setText(null);
valueArea.setEnabled(false);
}
}
/**
* Vérifie si de nouvelles instructions SQL ont été
* ajoutées à la suite des instruction déjà déclarées.
*/
protected void update() {
final int size = userSQL.size();
if (size > lastSize) {
fireIntervalAdded(this, lastSize, size-1);
lastSize = size;
}
}
/**
* Méthode appelée automatiquement lorsque l'utilisateur
* clique sur le bouton "Rétablir".
*/
public void actionPerformed(final ActionEvent event) {
reset();
}
}
/**
* Construit un éditeur d'instructions SQL.
*
* @param configuration Base de données dont on veut éditer la configuration.
* @param description Note explicative destinée à l'utilisateur.
* @param logger Journal dans lequel écrire une notification des
* requêtes qui ont été changées.
*/
public ConfigurationEditor(final Database configuration,
final String description,
final Logger logger)
{
super(new BorderLayout());
this.logger = logger;
this.configuration = configuration;
if (configuration == null) {
throw new NullPointerException("Configuration must not be null.");
}
sqlList.addListSelectionListener(model);
sqlList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
valueArea.setFont(new Font("monospaced", Font.PLAIN, 12));
setPreferredSize(new Dimension(720, 320));
final JScrollPane scrollList = new JScrollPane(sqlList);
final JScrollPane scrollValue = new JScrollPane(valueArea);
final JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, scrollList, scrollValue);
final JComponent comments = SwingUtilities.getMultilineLabelFor(scrollList, description);
comments.setBorder(BorderFactory.createEmptyBorder(/*top*/0, /*left*/0, /*bottom*/18, /*right*/0));
add(comments, BorderLayout.NORTH);
add(splitPane, BorderLayout.CENTER);
}
/**
* Fait apparaître l'éditeur des instructions SQL. Si l'utilisateur clique sur "Ok",
* alors les instructions éditées seront sauvegardées par un appel à la méthode
* {@link #save}.
*
* @param owner Composante par-dessus laquelle faire apparaître la boîte de dialogue.
* @return {@code true} si l'utilisateur à cliqué sur "Ok", ou {@code false} sinon.
*/
public boolean showDialog(final Component owner) {
if (userSQL.isEmpty()) {
// Il n'y a rien à afficher.
return false;
}
model.update();
sqlList.setSelectedIndex(0);
// TODO: JOptionPane ne fait pas un bon travail concernant la taille des boutons
// que l'on ajoute sur la barre des boutons (en plus de "Ok" et "Annuler").
// Pour afficher le bouton "Rétablir" malgré ces défauts, ne pas mettre
// 'model' en commentaire.
final boolean ok = SwingUtilities.showOptionDialog(owner, this, "Configuration"/*, model*/);
model.commit();
if (ok) save();
return ok;
}
/**
* Ajoute un caractère de changement de ligne ('\n')
* à la fin de texte spécifié s'il n'y en avait pas
* déjà un.
*/
private static String line(String value) {
if (value == null) {
return "";
}
final int length = value.length();
if (length != 0) {
final char c = value.charAt(length-1);
if (c!='\r' && c!='\n') {
value += '\n';
}
}
return value;
}
/**
* Ajoute une instruction SQL à la liste des instructions qui pourront être éditées.
*
* @param key Clé permetant de retrouver l'instruction SQL actuelle dans l'objet {@link Database}.
*/
public synchronized void addKey(final ConfigurationKey key) {
userSQL.add(line(configuration.getProperty(key)));
keySQL.add(key);
}
/**
* Enregistre les modifications apportées aux instructions SQL. Cette
* méthode sera appelée automatiquement lorsque l'utilisateur appuie
* sur "Ok" dans la boîte de dialogue.
*/
protected void save() {
for (int i=userSQL.size(); --i>=0;) {
final ConfigurationKey key = keySQL.get(i);
String value = userSQL.get(i);
if (value != null) {
value = value.trim();
if (value.length() == 0) {
value = null;
}
}
if (!Utilities.equals(value, configuration.getProperty(key))) {
final int clé;
if (Utilities.equals(value, key.getDefaultValue())) {
clé = ResourceKeys.REMOVE_QUERY_$1;
value = null;
} else {
clé = ResourceKeys.DEFINE_QUERY_$1;
}
configuration.setProperty(key, value);
if (logger != null) {
logger.config(Resources.format(clé, key.getKey()));
}
}
}
}
/**
* Rétablit les requêtes par défaut.
*/
private void reset() {
for (int i=userSQL.size(); --i>=0;) {
userSQL.set(i, line(keySQL.get(i).getDefaultValue()));
}
model.valueTextChanged();
}
}