/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.components.widget; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.image.RGBImageFilter; import java.beans.PropertyChangeSupport; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import org.openflexo.AdvancedPrefs; import org.openflexo.antar.binding.BindingDefinition; import org.openflexo.antar.binding.BindingDefinition.BindingDefinitionType; import org.openflexo.fib.FIBLibrary; import org.openflexo.fib.controller.FIBController; import org.openflexo.fib.model.DataBinding; import org.openflexo.fib.model.FIBBrowser; import org.openflexo.fib.model.FIBComponent; import org.openflexo.fib.model.FIBCustom; import org.openflexo.fib.model.FIBCustom.FIBCustomComponent; import org.openflexo.fib.model.FIBList; import org.openflexo.fib.view.FIBView; import org.openflexo.fib.view.widget.FIBBrowserWidget; import org.openflexo.fib.view.widget.FIBListWidget; import org.openflexo.foundation.FlexoModelObject; import org.openflexo.foundation.rm.FlexoProject; import org.openflexo.icon.IconFactory; import org.openflexo.icon.IconLibrary; import org.openflexo.icon.IconMarker; import org.openflexo.swing.TextFieldCustomPopup; import org.openflexo.toolbox.HasPropertyChangeSupport; import org.openflexo.toolbox.StringUtils; import org.openflexo.view.controller.FlexoController; import org.openflexo.view.controller.FlexoFIBController; /** * Widget allowing to select an object while browsing a relevant subset of objects in project * * @author sguerin * */ public abstract class FIBModelObjectSelector<T> extends TextFieldCustomPopup<T> implements FIBCustomComponent<T, FIBModelObjectSelector>, HasPropertyChangeSupport { static final Logger logger = Logger.getLogger(FIBModelObjectSelector.class.getPackage().getName()); private static final String DELETED = "deleted"; public abstract File getFIBFile(); private T _revertValue; protected SelectorDetailsPanel _selectorPanel; private FlexoProject project; private Object selectedObject; private T selectedValue; private final List<T> matchingValues; private T candidateValue; private FIBCustom component; private FIBController controller; private PropertyChangeSupport pcSupport; private FlexoController flexoController; private boolean isFiltered = false; private boolean showReset = true; public static BindingDefinition SELECTABLE = new BindingDefinition("selectable", Boolean.class, BindingDefinitionType.GET, false); public FIBModelObjectSelector(T editedObject) { super(editedObject); pcSupport = new PropertyChangeSupport(this); setRevertValue(editedObject); setFocusable(true); matchingValues = new ArrayList<T>(); getTextField().setEditable(true); getTextField().getDocument().addDocumentListener(new DocumentListener() { @Override public void removeUpdate(DocumentEvent e) { if (!textIsBeeingProgrammaticallyEditing()) { updateMatchingValues(); } } @Override public void insertUpdate(DocumentEvent e) { if (!textIsBeeingProgrammaticallyEditing()) { updateMatchingValues(); } } @Override public void changedUpdate(DocumentEvent e) { if (!textIsBeeingProgrammaticallyEditing()) { updateMatchingValues(); } } }); getTextField().addKeyListener(new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { updateMatchingValues(); if (matchingValues.size() > 0) { setSelectedValue(matchingValues.get(0)); apply(); } } }); } else if (e.getKeyCode() == KeyEvent.VK_UP) { getFIBListWidget().getDynamicJComponent().requestFocusInWindow(); getFIBListWidget().getDynamicJComponent().setSelectedIndex( getFIBListWidget().getDynamicJComponent().getModel().getSize() - 1); } else if (e.getKeyCode() == KeyEvent.VK_DOWN) { getFIBListWidget().getJComponent().requestFocusInWindow(); getFIBListWidget().getDynamicJComponent().setSelectedIndex(0); } } @Override public void keyTyped(KeyEvent e) { // if command-key is pressed, do not open popup if (e.isAltDown() || e.isAltGraphDown() || e.isControlDown() || e.isMetaDown()) { return; } boolean requestFocus = getTextField().hasFocus(); final int selectionStart = getTextField().getSelectionStart() + 1; final int selectionEnd = getTextField().getSelectionEnd() + 1; if (!popupIsShown()) { openPopup(); } // updateMatchingValues(); if (requestFocus) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { getTextField().requestFocusInWindow(); getTextField().select(selectionStart, selectionEnd); } }); } /*SwingUtilities.invokeLater(new Runnable() { @Override public void run() { int selectionStart = getTextField().getSelectionStart(); int selectionEnd = getTextField().getSelectionEnd(); System.out.println("Was selected: " + selectionStart + " and " + selectionEnd); if (!popupIsShown()) { openPopup(); } updateMatchingValues(); System.out.println("Now select: " + selectionStart + " and " + selectionEnd); getTextField().select(selectionStart, selectionEnd); } });*/ } }); } @Override public void delete() { super.delete(); if (pcSupport != null) { pcSupport.firePropertyChange(DELETED, false, true); } matchingValues.clear(); pcSupport = null; selectedObject = null; selectedValue = null; project = null; } @Override public void init(FIBCustom component, FIBController controller) { this.component = component; this.controller = controller; } @Override public void openPopup() { super.openPopup(); // System.out.println("Request focus now"); getTextField().requestFocusInWindow(); } public boolean isShowReset() { return showReset; } public void setShowReset(boolean showReset) { this.showReset = showReset; } @Override public PropertyChangeSupport getPropertyChangeSupport() { return pcSupport; } @Override public String getDeletedProperty() { return DELETED; } public boolean isFiltered() { return StringUtils.isNotEmpty(getFilteredName()) && isFiltered; } public String getFilteredName() { // return filteredName; return getTextField().getText(); } public void setFilteredName(String aString) { // logger.info("setFilteredName with "+aString); getTextField().setText(aString); // filteredName = aString; // updateMatchingValues(); } public Object getSelectedObject() { return selectedObject; } public void setSelectedObject(Object selectedObject) { // System.out.println("set selected object: "+selectedObject); Object old = getSelectedObject(); this.selectedObject = selectedObject; pcSupport.firePropertyChange("selectedObject", old, selectedObject); if (isAcceptableValue(selectedObject)) { setSelectedValue((T) selectedObject); } else { setSelectedValue(null); } } public T getSelectedValue() { return selectedValue; } public void setSelectedValue(T selectedValue) { // System.out.println("set selected value: "+selectedValue); T old = getSelectedValue(); this.selectedValue = selectedValue; pcSupport.firePropertyChange("selectedValue", old, selectedValue); if (getSelectedObject() != getSelectedValue()) { setSelectedObject(selectedValue); } } private void updateMatchingValues() { final List<T> oldMatchingValues = new ArrayList<T>(getMatchingValues()); // System.out.println("updateMatchingValues() with " + getFilteredName()); matchingValues.clear(); if (getAllSelectableValues() != null && getFilteredName() != null) { isFiltered = true; for (T next : getAllSelectableValues()) { if (isAcceptableValue(next) && matches(next, getFilteredName())) { matchingValues.add(next); } } } // logger.info("Objects matching with " + getFilteredName() + " found " + matchingValues.size() + " values"); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { pcSupport.firePropertyChange("matchingValues", oldMatchingValues, getMatchingValues()); if (matchingValues.size() == 1) { setSelectedValue(matchingValues.get(0)); } } }); /*pcSupport.firePropertyChange("matchingValues", oldMatchingValues, getMatchingValues()); if (matchingValues.size() == 1) { setSelectedValue(matchingValues.get(0)); }*/ } private void clearMatchingValues() { isFiltered = false; List<T> oldMatchingValues = new ArrayList<T>(getMatchingValues()); matchingValues.clear(); pcSupport.firePropertyChange("matchingValues", oldMatchingValues, null); } private FIBBrowserWidget getFIBBrowserWidget() { return ((SelectorDetailsPanel) getCustomPanel()).retrieveFIBBrowserWidget(); } private FIBListWidget getFIBListWidget() { return ((SelectorDetailsPanel) getCustomPanel()).retrieveFIBListWidget(); } /** * This method is used to retrieve all potential values when implementing completion<br> * Completion will be performed on that selectable values<br> * Default implementation is to iterate on all values of browser, please take care to infinite loops.<br> * * Override when required */ protected Collection<T> getAllSelectableValues() { return ((SelectorDetailsPanel) getCustomPanel()).getAllSelectableValues(); } /** * Override when required */ protected boolean matches(T o, String filteredName) { if (o instanceof FlexoModelObject) { return ((FlexoModelObject) o).getName() != null && ((FlexoModelObject) o).getName().toUpperCase().indexOf(filteredName.toUpperCase()) > -1; } return false; } public List<T> getMatchingValues() { return matchingValues; } public FlexoProject getProject() { return project; } @CustomComponentParameter(name = "project", type = CustomComponentParameter.Type.MANDATORY) public void setProject(FlexoProject project) { if (project == null) { if (logger.isLoggable(Level.WARNING)) { logger.warning("Passing null project. If you rely on project this is unlikely to work"); } } this.project = project; pcSupport.firePropertyChange("project", null, project); } @Override public void setRevertValue(T oldValue) { if (oldValue != null) { _revertValue = oldValue; } else { _revertValue = null; } if (logger.isLoggable(Level.FINE)) { logger.fine("Sets revert value to " + _revertValue); } } @Override public T getRevertValue() { return _revertValue; } @Override protected ResizablePanel createCustomPanel(T editedObject) { _selectorPanel = makeCustomPanel(editedObject); if (flexoController != null) { _selectorPanel.getController().setFlexoController(flexoController); } return _selectorPanel; } protected SelectorDetailsPanel makeCustomPanel(T editedObject) { return new SelectorDetailsPanel(editedObject); } @Override public void updateCustomPanel(T editedObject) { // logger.info("updateCustomPanel with " + editedObject + " _selectorPanel=" + _selectorPanel); setSelectedObject(editedObject); if (_selectorPanel != null) { _selectorPanel.update(); } } protected SelectorFIBController makeCustomFIBController(FIBComponent fibComponent) { return new SelectorFIBController(fibComponent, FIBModelObjectSelector.this); } public class SelectorDetailsPanel extends ResizablePanel { private final FIBComponent fibComponent; private final FIBView fibView; private final SelectorFIBController controller; protected SelectorDetailsPanel(T anObject) { super(); fibComponent = FIBLibrary.instance().retrieveFIBComponent(getFIBFile()); controller = makeCustomFIBController(fibComponent); fibView = controller.buildView(fibComponent); controller.setDataObject(FIBModelObjectSelector.this, true); setLayout(new BorderLayout()); add(fibView.getResultingJComponent(), BorderLayout.CENTER); selectValue(anObject); } private void selectValue(T value) { FIBBrowserWidget browserWidget = retrieveFIBBrowserWidget(); if (browserWidget != null) { // Force reselect value because tree may have been recomputed browserWidget.setSelectedObject(value, true); } } public void update() { controller.setDataObject(FIBModelObjectSelector.this); // logger.info("update() selectedValue=" + getSelectedValue() + " selectedObject=" + getSelectedObject()); selectValue(getSelectedValue()); } @Override public Dimension getDefaultSize() { return new Dimension(fibComponent.getWidth(), fibComponent.getHeight()); } public void delete() { } protected Set<T> getAllSelectableValues() { Set<T> returned = new HashSet<T>(); FIBBrowserWidget browserWidget = retrieveFIBBrowserWidget(); if (browserWidget == null) { return null; } Iterator<Object> it = browserWidget.getBrowserModel().retrieveContents(); while (it.hasNext()) { Object o = it.next(); if (getRepresentedType().isAssignableFrom(o.getClass())) { returned.add((T) o); } } return returned; } private FIBBrowserWidget retrieveFIBBrowserWidget() { List<FIBComponent> listComponent = fibComponent.retrieveAllSubComponents(); for (FIBComponent c : listComponent) { if (c instanceof FIBBrowser) { return (FIBBrowserWidget) controller.viewForComponent(c); } } return null; } private FIBListWidget retrieveFIBListWidget() { List<FIBComponent> listComponent = fibComponent.retrieveAllSubComponents(); for (FIBComponent c : listComponent) { if (c instanceof FIBList) { return (FIBListWidget) controller.viewForComponent(c); } } return null; } public FIBComponent getFIBComponent() { return fibComponent; } public SelectorFIBController getController() { return controller; } } public static class SelectorFIBController extends FlexoFIBController { private final FIBModelObjectSelector selector; public SelectorFIBController(FIBComponent component, FIBModelObjectSelector selector) { super(component); this.selector = selector; } public void selectedObjectChanged() { selector.setEditedObject(selector.selectedValue); } public void apply() { selector.apply(); } public void cancel() { selector.cancel(); } public void reset() { selector.setEditedObject(null); selector.setSelectedObject(null); selector.setSelectedValue(null); selector.apply(); } protected final Icon decorateIcon(FlexoModelObject object, Icon returned) { if (AdvancedPrefs.getHightlightUncommentedItem() && object != null && object.isDescriptionImportant() && !object.hasDescription()) { if (returned instanceof ImageIcon) { returned = IconFactory.getImageIcon((ImageIcon) returned, new IconMarker[] { IconLibrary.WARNING }); } else { logger.severe("CANNOT decorate a non ImageIcon for " + this); } } return returned; } class ColorSwapFilter extends RGBImageFilter { private final int target1; private final int replacement1; private final int target2; private final int replacement2; public ColorSwapFilter(Color target1, Color replacement1, Color target2, Color replacement2) { this.target1 = target1.getRGB(); this.replacement1 = replacement1.getRGB(); this.target2 = target2.getRGB(); this.replacement2 = replacement2.getRGB(); } @Override public int filterRGB(int x, int y, int rgb) { if (rgb == target1) { return replacement1; } else if (rgb == target2) { return replacement2; } return rgb; } } } /* @Override public void setEditedObject(BackgroundStyle object) { logger.info("setEditedObject with "+object); super.setEditedObject(object); } */ @Override public void apply() { clearMatchingValues(); setEditedObject(getSelectedValue()); setRevertValue(getEditedObject()); closePopup(); super.apply(); } @Override public void cancel() { if (logger.isLoggable(Level.FINE)) { logger.fine("CANCEL: revert to " + getRevertValue()); } setEditedObject(getRevertValue()); closePopup(); super.cancel(); } @Override protected void deletePopup() { if (_selectorPanel != null) { _selectorPanel.delete(); } _selectorPanel = null; super.deletePopup(); } /* * protected void pointerLeavesPopup() { cancel(); } */ public FIBComponent getFIBComponent() { if (getSelectorPanel() != null) { return getSelectorPanel().getFIBComponent(); } return null; } protected SelectorFIBController getController() { if (getSelectorPanel() == null) { return null; } return getSelectorPanel().getController(); } protected FIBBrowser getFIBBrowser() { if (getFIBComponent() == null) { return null; } List<FIBComponent> listComponent = getFIBComponent().retrieveAllSubComponents(); for (FIBComponent c : listComponent) { if (c instanceof FIBBrowser) { return (FIBBrowser) c; } } return null; } protected FIBBrowserWidget retrieveFIBBrowserWidget() { if (getFIBComponent() == null) { return null; } if (getController() == null) { return null; } List<FIBComponent> listComponent = getFIBComponent().retrieveAllSubComponents(); for (FIBComponent c : listComponent) { if (c instanceof FIBBrowser) { return (FIBBrowserWidget) getController().viewForComponent(c); } } return null; } public SelectorDetailsPanel getSelectorPanel() { return _selectorPanel; } @Override public FIBModelObjectSelector getJComponent() { return this; } @Override public String renderedString(T editedObject) { if (editedObject == null) { return ""; } if (editedObject instanceof FlexoModelObject) { return ((FlexoModelObject) editedObject).getFullyQualifiedName(); } return editedObject.toString(); } /** * Override when required */ protected boolean isAcceptableValue(Object o) { if (o == null) { return false; } if (!getRepresentedType().isAssignableFrom(o.getClass())) { return false; } return evaluateSelectableCondition((T) o); } private String _selectableConditionAsString = null; private DataBinding _selectableCondition; public DataBinding getSelectableConditionDataBinding() { if (_selectableCondition != null) { return _selectableCondition; } if (_selectableConditionAsString == null || StringUtils.isEmpty(_selectableConditionAsString)) { return null; } _selectableCondition = new DataBinding(_selectableConditionAsString); _selectableCondition.setOwner(component); _selectableCondition.setBindingAttribute(null); _selectableCondition.setBindingDefinition(SELECTABLE); // System.out.println("setSelectableCondition with "+_selectableCondition+" valid ? "+_selectableCondition.isValid()); return _selectableCondition; } public String getSelectableCondition() { return _selectableConditionAsString; } @CustomComponentParameter(name = "selectableCondition", type = CustomComponentParameter.Type.OPTIONAL) public void setSelectableCondition(String aCondition) { _selectableConditionAsString = aCondition; _selectableCondition = null; } public boolean evaluateSelectableCondition(T candidateValue) { if (getSelectableConditionDataBinding() == null) { return true; } setCandidateValue(candidateValue); boolean returned = (Boolean) getSelectableConditionDataBinding().getBindingValue(controller); return returned; } // Used for computation of "isAcceptableValue()?" public T getCandidateValue() { return candidateValue; } // Used for computation of "isAcceptableValue()?" public void setCandidateValue(T candidateValue) { this.candidateValue = candidateValue; } public FlexoController getFlexoController() { return flexoController; } public void setFlexoController(FlexoController flexoController) { this.flexoController = flexoController; if (_selectorPanel != null) { _selectorPanel.getController().setFlexoController(flexoController); } } /*public static void testSelector(final FIBModelObjectSelector selector) { final FlexoProject prj = loadProject(); selector.createCustomPanel(null); selector.setProject(prj); FIBAbstractEditor editor = new FIBAbstractEditor() { @Override public Object[] getData() { return FIBAbstractEditor.makeArray(selector); } @Override public File getFIBFile() { return selector.getFIBFile(); } @Override public FIBController getController() { return selector.getSelectorPanel().controller; } public FIBController makeNewController() { return selector.makeCustomFIBController(selector.getSelectorPanel().fibComponent); } }; editor.addAction("set_filtered_name", new ActionListener() { @Override public void actionPerformed(ActionEvent e) { selector.setFilteredName("T"); selector.updateMatchingValues(); } }); editor.launch(); } protected static final FlexoEditorFactory EDITOR_FACTORY = new FlexoEditorFactory() { @Override public DefaultFlexoEditor makeFlexoEditor(FlexoProject project) { return new DefaultFlexoEditor(project); } }; public static FileResource PRJ_FILE = new FileResource("Prj/TestBrowser.prj"); public static FlexoProject loadProject() { File projectFile = PRJ_FILE; FlexoProject project = null; FlexoEditor editor; logger.info("Found project " + projectFile.getAbsolutePath()); try { editor = FlexoResourceManager.initializeExistingProject(projectFile, EDITOR_FACTORY, null); project = editor.getProject(); } catch (ProjectLoadingCancelledException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ProjectInitializerException e) { // TODO Auto-generated catch block e.printStackTrace(); } logger.info("Successfully loaded project " + projectFile.getAbsolutePath()); return project; }*/ }