/**
* 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.sif.components.filter;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.beans.EventHandler;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import org.orbisgis.commons.events.EventException;
import org.orbisgis.commons.events.Listener;
import org.orbisgis.commons.events.ListenerContainer;
import org.orbisgis.sif.common.ContainerItemProperties;
import org.orbisgis.sif.components.CustomButton;
import org.orbisgis.sif.icons.SifIcon;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;
/**
* filter GUI and functionality is a generic concept of OrbisGIS GUI.
* This manager help to
* - introduce easily a filtering system
* - show the same GUI for the same functionality over all frames
* @param <FilterInterface> The filter interface implements methods generated by
* filter factories, this is specific to the DataModel
* @param <FilterSerialisation>
*
*/
public class FilterFactoryManager<FilterInterface,FilterSerialisation extends ActiveFilter> {
/**
* Use this interface to register a listener
*/
public interface FilterChangeListener extends Listener<FilterChangeEventData> {
}
private static final I18n I18N = I18nFactory.getI18n(FilterFactoryManager.class);
private static final Logger LOGGER = LoggerFactory.getLogger(FilterFactoryManager.class);
private JPanel filterListPanel;/*!< This panel contain the set of filters */
//List of active filters
private Map<Component,FilterSerialisation> filterValues = Collections.synchronizedMap(new HashMap<Component,FilterSerialisation>());
//List of filter factories
private Map<String,FilterFactory<FilterInterface,FilterSerialisation>> filterFactories = Collections.synchronizedMap(new HashMap<String,FilterFactory<FilterInterface,FilterSerialisation>>());
//Factory index, this retrieve the factory name from an integer in
//all JComboBox filter factories
private List<ContainerItemProperties> filterFactoriesComboLabels = new ArrayList<ContainerItemProperties>();
//The factory shown when the user click on new factory button
private String defaultFilterFactory="";
//Listener on filter change
private ListenerContainer<FilterChangeEventData> eventFilterChange = new ListenerContainer<FilterChangeEventData>();
private ListenerContainer<FilterChangeEventData> eventFilterFactoryChange = new ListenerContainer<FilterChangeEventData>();
private boolean userCanRemoveFilter = true;
/**
* Add a filter factory
* @param filterFactory The filter factory instance
*/
public final void registerFilterFactory(FilterFactory<FilterInterface,FilterSerialisation> filterFactory) {
//Add filter factory in the HashMap
filterFactories.put(filterFactory.getFactoryId(), filterFactory);
//Add filter factory label and id in a list (for all GUI ComboBox)
filterFactoriesComboLabels.add(new ContainerItemProperties(filterFactory.getFactoryId(),filterFactory.getFilterLabel()));
if(defaultFilterFactory.isEmpty()) {
defaultFilterFactory = filterFactory.getFactoryId();
}
//TODO if some filters are already shown, refresh all factories combo box (Future Plugin-filter ?)
}
/**
* Remove all filters registered with the provided factory id
* @param factoryId The factory id returned by DataSourceFilterFactory.getFactoryId
*/
public void removeFilters(String factoryId) {
//Collect all components to remove
Stack<Component> filterPanelsToRemove = new Stack<Component>();
for(Map.Entry<Component,FilterSerialisation> filter : filterValues.entrySet()) {
if(filter.getValue().getFactoryId().equals(factoryId)) {
//Found a filter panel registered by factoryId
filterPanelsToRemove.add(filter.getKey());
}
}
//Remove components
while(!filterPanelsToRemove.isEmpty()) {
onRemoveFilter(filterPanelsToRemove.pop());
}
}
/**
* Call by listeners when the user click on the Remove button
* or change the factory combobox value
* @param filterPanel The filter panel instance
*/
public void onRemoveFilter(Component filterPanel) {
//Remove the filter value
filterValues.remove(filterPanel);
//Remove the filter panel from the GUI filter list panel
filterListPanel.remove(filterPanel);
filterListPanel.updateUI();
//Update filters
fireFilterFactoryChange();
}
/**
* The default factory when the user click on add filter button
* @param defaultFilterFactory The name of the factory
*/
public void setDefaultFilterFactory(String defaultFilterFactory) {
this.defaultFilterFactory = defaultFilterFactory;
}
/**
* The user click on add filter button
*/
public void onAddFilter() {
//Add the default filter
FilterFactory<FilterInterface,FilterSerialisation> factory = filterFactories.get(defaultFilterFactory);
addFilter(factory.getDefaultFilterValue());
}
/**
* Add the swing filter component to the filter gui
* This method will add a remove filter button,
* a filter factory JComboBox and the specified component
* @param newFilterComponent Returned by a DataSourceFilterFactory
* @warning onFilterChanged Must retrieve the FilterFactoriesComboBox !
*/
private void addFilterComponent(Component newFilterComponent,FilterSerialisation activeFilter) {
//the factory name
JPanel filterPanel = new JPanel(new BorderLayout());
if(userCanRemoveFilter) {
//Create the remove button
JButton removeButton = makeRemoveFilterButton();
//Attach listener, will call the onRemoveFilter method
//with the parent container as argument
removeButton.addActionListener(
EventHandler.create(
ActionListener.class, this, "onRemoveFilter","source.parent")
);
//Add the button in the filter panel
filterPanel.add(removeButton,BorderLayout.WEST);
}
//Create a layout to contain the factory and filter components
JPanel factoryAndFilter = new JPanel(new BorderLayout());
if(filterFactories.size()>1) {
//Create the filter factory combobox
factoryAndFilter.add(makeFilterFactoriesComboBox(activeFilter.getFactoryId()),BorderLayout.WEST);
} else {
factoryAndFilter.add(makeFileFactoryLabel(filterFactories.get(activeFilter.getFactoryId()).getFilterLabel()),BorderLayout.WEST);
}
if(newFilterComponent!=null) {
//Add the factory component in the filter panel
factoryAndFilter.add(newFilterComponent,BorderLayout.CENTER);
}
filterPanel.add(factoryAndFilter,BorderLayout.CENTER);
//Add the component in the filter list contents
filterValues.put(filterPanel, activeFilter);
//Add the component in the filter list GUI
filterListPanel.add(filterPanel);
//Refresh the GUI
filterListPanel.updateUI();
}
/**
* @see FilterFactoryManager#setUserCanRemoveFilter(boolean)
* @return False if the next remove buttons will not be created
*/
public boolean isUserCanRemoveFilter() {
return userCanRemoveFilter;
}
/**
* Hide/Show The remove button at the left of filters
* This parameter is only active on new filters
* @param userCanRemoveFilter
*/
public void setUserCanRemoveFilter(boolean userCanRemoveFilter) {
this.userCanRemoveFilter = userCanRemoveFilter;
}
/**
* Remove all filters
* Do not fire events
*/
public void clearFilters() {
filterValues.clear();
filterListPanel.removeAll();
filterListPanel.updateUI();
}
/**
* Get all filter values currently shown
* @return
*/
public Collection<FilterSerialisation> getFilterValues() {
return filterValues.values();
}
/**
* Create a new filter in the UI filter list
* @param activeFilter The filter value
*/
public void addFilter(FilterSerialisation activeFilter) {
if(filterFactories.containsKey(activeFilter.getFactoryId())) {
//Retrieve the factory
FilterFactory<FilterInterface,FilterSerialisation> filterFactory = filterFactories.get(activeFilter.getFactoryId());
if(filterFactory==null) {
throw new IllegalArgumentException(I18N.tr("The provided filter factory name has not been registered"));
}
//If the filter value is modified reloadFilters must be called
activeFilter.addPropertyChangeListener(ActiveFilter.PROP_CURRENTFILTERVALUE,
EventHandler.create(PropertyChangeListener.class, this,"onFilterChanged"));
//Create the Swing component
Component swingFiterField = filterFactory.makeFilterField(activeFilter);
addFilterComponent(swingFiterField, activeFilter);
//Update the filters
fireFilterFactoryChange();
}
}
/**
* Use the listener manager to track change on filters
* Your list control must be updated with a new set of filters,
* through the getFilters method
* @return The listener manager
*/
public ListenerContainer<FilterChangeEventData> getEventFilterChange() {
return eventFilterChange;
}
/**
* Use the listener manager to track change on filters factories
* Your list control must be updated with a new set of filters,
* through the getFilters method
* @return The listener manager
*/
public ListenerContainer<FilterChangeEventData> getEventFilterFactoryChange() {
return eventFilterFactoryChange;
}
/**
* Replace all FactoryComboBox by Labels
* Navigation through components is quite difficult and verbose.
* A reference could be used instead of doing the kind of navigation
*/
private void replaceFactoryComboBoxByLabels() {
boolean uiChange=false;
for (Component removeButtonFactoryFilter : filterListPanel.getComponents()) {
if(removeButtonFactoryFilter instanceof JPanel) {
Component factoryAndFilter = ((BorderLayout)((JPanel)removeButtonFactoryFilter).getLayout()).getLayoutComponent(BorderLayout.CENTER);
if(factoryAndFilter!=null) {
Component factoryList = ((BorderLayout)((JPanel)factoryAndFilter).getLayout()).getLayoutComponent(BorderLayout.WEST);
if(!(factoryList instanceof JComboBox || factoryList instanceof JLabel)) {
//Could not find filter Factory list
//You must update onFilterChanged according to the change on filter Factory ComboBox panel layout
LOGGER.debug("Error: Could not find filter Factory list");
} else {
if(factoryList instanceof JComboBox) {
String itemLabel = ((JComboBox)factoryList).getSelectedItem().toString();
//Remove the factory list
((JPanel)factoryAndFilter).remove(factoryList);
//Place the Label
((JPanel)factoryAndFilter).add(makeFileFactoryLabel(itemLabel), BorderLayout.WEST);
((JPanel)factoryAndFilter).doLayout();
uiChange=true;
}
}
}
}
}
if(uiChange) {
filterListPanel.updateUI();
}
}
/**
* The user Add/Change/Remove filter type (factory).
* @return true if the event has been accepted by all listeners
*/
private boolean fireFilterFactoryChange() {
try {
//Fire event
eventFilterFactoryChange.callListeners(new FilterChangeEventData(this));
return true;
} catch (EventException ex) {
//The event has been refused by a listener
return false;
}
}
/**
* Fire the filter change event
* The List must update the content according to the filters
* @return true if the event has been accepted by all listeners
*/
private boolean fireFilterChange() {
try {
//Fire event
eventFilterChange.callListeners(new FilterChangeEventData(this));
return true;
} catch (EventException ex) {
//The event has been refused by a listener
return false;
}
}
/**
* The input of a filter has been edited by the user
*/
public void onFilterChanged() {
//The user change the content of the filter
//Then the user accept the current factories
//Replace all factories by labels to free spaces
//Only if the user is able to remove factories
if(userCanRemoveFilter) {
replaceFactoryComboBoxByLabels();
}
fireFilterChange();
}
/**
* Regenerate all filters from filters components
* @return All active filters
*/
public List<FilterInterface> getFilters() {
List<FilterInterface> generatedFilters = new ArrayList<FilterInterface>();
//For each active filter
for(FilterSerialisation activeFilter : filterValues.values()) {
if(filterFactories.containsKey(activeFilter.getFactoryId())) {
//Retrieve the factory
FilterFactory<FilterInterface,FilterSerialisation> filterFactory = filterFactories.get(activeFilter.getFactoryId());
//Ask the factory to build the filter with the current value
FilterInterface generatedFilter = filterFactory.getFilter(activeFilter);
generatedFilters.add(generatedFilter);
}
}
//Set the filters and update the list
return generatedFilters;
}
/**
* The user selected a filter factory
* A new filter is shown with the selected filter factory
* @param filterFactoryId The filter factory name
*/
public void onChooseFilterFactory(String filterFactoryId) {
//Add a new filter with an empty value
FilterFactory<FilterInterface,FilterSerialisation> factory = filterFactories.get(filterFactoryId);
addFilter(factory.getDefaultFilterValue());
}
private JLabel makeFileFactoryLabel(String selectedFactory) {
return new JLabel(selectedFactory);
}
/**
* Create a new filter factories combo box
* @return A new instance of filterFactoriesComboBox
*/
private JComboBox makeFilterFactoriesComboBox(String selectedFactory) {
//Set a unique data model for all filterFactoriesCombo
JComboBox filterFactoriesCombo = new JComboBox(this.filterFactoriesComboLabels.toArray());
//Select the factory
filterFactoriesCombo.setSelectedItem(new ContainerItemProperties(selectedFactory, ""));
//Add a listener to remove the filter
filterFactoriesCombo.addActionListener(
EventHandler.create(ActionListener.class, this,
"onRemoveFilter","source.parent.parent"));
//Add a listener to add a new filter with the selected factory
filterFactoriesCombo.addActionListener(
EventHandler.create(ActionListener.class, this,
"onChooseFilterFactory","source.selectedItem.getKey"));
return filterFactoriesCombo;
}
/**
* Build the remove filter button component
* @return The button
* @note listener are created in this function
*/
private JButton makeRemoveFilterButton() {
//Create a compact button
JButton removeFilterButton = new CustomButton(SifIcon.getIcon("delete"));
removeFilterButton.setToolTipText(I18N.tr("Delete this filter"));
return removeFilterButton;
}
/**
* Build the add filter button component
* @return The button
* @note listener are created in this function
*/
private Component makeAddFilterButton() {
//This JPanel set the button at the top
JPanel buttonAlignement = new JPanel(new BorderLayout());
//Create a compact button
JButton addFilterButton = new CustomButton(SifIcon.getIcon("add_filter"));
buttonAlignement.add(addFilterButton,BorderLayout.NORTH);
//Toottip
addFilterButton.setToolTipText(I18N.tr("Add a new filter"));
//Apply action listener
addFilterButton.addActionListener(
EventHandler.create(ActionListener.class, this, "onAddFilter")
);
return buttonAlignement;
}
/**
* Create the filter panel
* @param createAddButton The manager will insert a button that will call the method onAddFilter
* @return The builded panel
*/
public JPanel makeFilterPanel(boolean createAddButton) {
//This panel contain the button panel and the filter list panel
JPanel buttonAndFilterList = new JPanel(new BorderLayout());
if(createAddButton) {
//Add the toggle button
buttonAndFilterList.add(makeAddFilterButton(), BorderLayout.LINE_START);
}
//GridLayout with 1 column (vertical stack) and n(0) rows
filterListPanel = new JPanel(new GridLayout(0,1));
//filter List must take all horizontal space
//CENTER will expand the content to take all avaible place
buttonAndFilterList.add(filterListPanel, BorderLayout.CENTER);
//Add the AddFilter button and filter list in the main filter panel
return buttonAndFilterList;
}
}