/* * (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.Dimension; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.text.Collator; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.Vector; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractListModel; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.openflexo.components.browser.ProjectBrowser; import org.openflexo.components.browser.SelectionController; import org.openflexo.foundation.FlexoEditor; import org.openflexo.foundation.FlexoModelObject; import org.openflexo.foundation.rm.FlexoProject; import org.openflexo.localization.FlexoLocalization; import org.openflexo.swing.CustomPopup; public abstract class AbstractSelectorPanel<T extends FlexoModelObject> extends CustomPopup.ResizablePanel { static final Logger logger = Logger.getLogger(AbstractSelectorPanel.class.getPackage().getName()); private final AbstractSelectorPanelOwner<T> _owner; ProjectBrowser _browser; protected BrowserChooserView _browserView; private JList list; private JScrollPane scrollList; private JButton _applyButton; private JButton _cancelButton; private JButton _resetButton; protected CompletionListModel _completionListModel; private Vector<T> sortedObjectList; private Vector<String> sortedNameList; private JPanel _controlPanel; protected ListSelectionListener listSelectionListener; protected MouseAdapter listMouseListener; public static interface AbstractSelectorPanelOwner<T> { public FlexoProject getProject(); public FlexoModelObject getRootObject(); public FlexoEditor getEditor(); public boolean isSelectable(FlexoModelObject object); public T getEditedObject(); public void setEditedObject(T object); public void apply(); public void cancel(); public String renderedString(T editedObject); public JTextField getTextField(); // used to manage completion public boolean popupIsShown(); public void openPopup(); public void closePopup(); public boolean isProgrammaticalySet(); public void setProgrammaticalySet(boolean aFlag); public KeyAdapter getCompletionListKeyAdapter(); public Integer getDefaultWidth(); public Integer getDefaultHeight(); } public AbstractSelectorPanel(AbstractSelectorPanelOwner<T> owner) { super(); _owner = owner; } @Override public Dimension getDefaultSize() { Dimension returned = _browserView.getDefaultSize(); returned.width = returned.width + 10; returned.height = returned.height + 40; if (_owner.getDefaultWidth() != null) { returned.width = _owner.getDefaultWidth(); } if (_owner.getDefaultHeight() != null) { returned.height = _owner.getDefaultHeight(); } return returned; } public FlexoModelObject getSelectedObject() { if (_browserView != null) { return _browserView.getSelectedObject(); } return null; } protected abstract ProjectBrowser createBrowser(FlexoProject project); T _lastBrowsed = null; public void setRootObject(FlexoModelObject aRootObject) { if (_browser != null) { _browser.setRootObject(aRootObject); } } protected void init() { setLayout(new BorderLayout()); _browser = createBrowser(_owner.getProject()); _browser.setHandlesControlPanel(false); _browser.setRootObject(_owner.getRootObject()); _browser.setSelectionController(new SelectionController() { @Override public boolean isSelectable(FlexoModelObject o) { return _owner.isSelectable(o); } }); // TODO: grab a hand on the FlexoController here. _browserView = new BrowserChooserView(_browser, null, _owner) { @Override public void objectWasSelected(FlexoModelObject object) { _owner.setEditedObject((T) object); } @Override public void objectWasDefinitelySelected(FlexoModelObject object) { _owner.setEditedObject((T) object); _owner.apply(); } }; sortedObjectList = new Vector<T>(); Iterator<FlexoModelObject> en = _browser.getAllObjects(); while (en.hasNext()) { T o = (T) en.next(); if (_owner.isSelectable(o)) { sortedObjectList.add(o); } } Collections.sort(sortedObjectList, new Comparator<T>() { private Collator collator = Collator.getInstance(); @Override public int compare(T o1, T o2) { String s1 = _owner.renderedString(o1); String s2 = _owner.renderedString(o2); if (s1 != null && s2 != null) { return collator.compare(s1, s2); } else { return 0; } } }); sortedNameList = new Vector<String>(); for (T object : sortedObjectList) { sortedNameList.add(_owner.renderedString(object)); } _completionListModel = new CompletionListModel(_owner.getTextField()); _completionListModel.setData(sortedNameList); list = new JList(_completionListModel); list.addKeyListener(_owner.getCompletionListKeyAdapter()); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); listSelectionListener = new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { T o = getCurrentSelectedInCompletionList(); if (o != null) { if (_lastBrowsed != null && _browser != null) { _browser.collapse(_lastBrowsed); } if (_browser != null) { _browser.fireResetSelection(); _browser.fireObjectSelected(o); } _lastBrowsed = o; // setEditedObject(o); /* ADDED on Apr 26th 2007, by SGU */ } } }; list.addListSelectionListener(listSelectionListener); listMouseListener = new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { if (e.getClickCount() == 2) { T o = getCurrentSelectedInCompletionList(); if (o != null) { _owner.setEditedObject(o); _owner.apply(); } } } }; list.addMouseListener(listMouseListener); completionListIsShown = false; _controlPanel = new JPanel(); _controlPanel.setOpaque(false); _controlPanel.setLayout(new FlowLayout()); _controlPanel.add(_applyButton = new JButton(FlexoLocalization.localizedForKey("ok"))); _controlPanel.add(_cancelButton = new JButton(FlexoLocalization.localizedForKey("cancel"))); _controlPanel.add(_resetButton = new JButton(FlexoLocalization.localizedForKey("reset"))); _applyButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { if (_lastBrowsed != null) { AbstractSelectorPanel.this._owner.setEditedObject(_lastBrowsed); } _owner.apply(); } }); _cancelButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { _owner.cancel(); } }); _resetButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { AbstractSelectorPanel.this._owner.setEditedObject(null); _owner.apply(); } }); add(_browserView, BorderLayout.CENTER); add(_controlPanel, BorderLayout.SOUTH); update(); } // return true if an object was correctly selected, false otherwise protected boolean processEnterPressed() { if (logger.isLoggable(Level.FINE)) { logger.fine("Pressed on ENTER"); } T o = getCurrentSelectedInCompletionList(); if (o != null) { _owner.setEditedObject(o); _owner.apply(); return true; } return false; } protected void processTabPressed() { if (logger.isLoggable(Level.FINE)) { logger.fine("Pressed on TAB, _completionListModel.size()=" + _completionListModel.getSize()); } if (_completionListModel.getSize() == 1) { T o = getObjectInCompletionListAtIndex(0); if (o != null) { _owner.setEditedObject(o); _owner.apply(); } } else if (_completionListModel.getSize() > 1) { if (logger.isLoggable(Level.FINE)) { logger.fine("Pressed on TAB, _completionListModel.size()=" + _completionListModel.getSize()); } String commonPrefix = _completionListModel.getCommonPrefix(); if (commonPrefix.equalsIgnoreCase(_owner.getTextField().getText())) { // Special case where text might be completed for (int i = 0; i < _completionListModel.getSize(); i++) { if (_completionListModel.getElementAt(i) != null) { if (_completionListModel.getElementAt(i).equals(_owner.getTextField().getText())) { T o = getObjectInCompletionListAtIndex(i); if (o != null) { _owner.setEditedObject(o); _owner.apply(); } } } } } else { // Just complete _owner.setProgrammaticalySet(true); _owner.getTextField().setText(_completionListModel.getCommonPrefix()); _owner.setProgrammaticalySet(false); } } } protected void processUpPressed() { int selectedIndex = list.getSelectedIndex(); if (logger.isLoggable(Level.FINE)) { logger.fine("processUpPressed() selectedIndex=" + selectedIndex + " size=" + list.getModel().getSize()); } if (selectedIndex > 0) { list.setSelectedIndex(selectedIndex - 1); list.ensureIndexIsVisible(selectedIndex - 1); } else if (selectedIndex == 0) { list.setSelectedIndex(list.getModel().getSize() - 1); list.ensureIndexIsVisible(list.getModel().getSize() - 1); } else if (selectedIndex == -1) { if (list.getModel().getSize() > 0) { list.setSelectedIndex(list.getModel().getSize() - 1); list.ensureIndexIsVisible(list.getModel().getSize() - 1); } } _owner.getTextField().requestFocusInWindow(); } protected void processDownPressed() { int selectedIndex = list.getSelectedIndex(); if (logger.isLoggable(Level.FINE)) { logger.fine("processDownPressed() selectedIndex=" + selectedIndex + " size=" + list.getModel().getSize()); } if (selectedIndex < list.getModel().getSize() - 1) { list.setSelectedIndex(selectedIndex + 1); list.ensureIndexIsVisible(selectedIndex + 1); } else if (selectedIndex == list.getModel().getSize() - 1) { list.setSelectedIndex(0); list.ensureIndexIsVisible(0); } else if (selectedIndex == -1) { if (list.getModel().getSize() > 0) { list.setSelectedIndex(0); list.ensureIndexIsVisible(0); } } _owner.getTextField().requestFocusInWindow(); } protected JPanel getControlPanel() { return _controlPanel; } protected T getCurrentSelectedInCompletionList() { if (list == null) { return null; } ListSelectionModel lsm = list.getSelectionModel(); int selectedRow = lsm.getMinSelectionIndex(); int selectedIndex = _completionListModel.getIndexOf(selectedRow); if (!lsm.isSelectionEmpty()) { if (selectedIndex >= 0 && selectedIndex < sortedObjectList.size()) { T returned = sortedObjectList.elementAt(selectedIndex); return returned; } } return null; } protected T getObjectInCompletionListAtIndex(int index) { int selectedIndex = _completionListModel.getIndexOf(index); if (selectedIndex >= 0 && selectedIndex < sortedObjectList.size()) { T returned = sortedObjectList.elementAt(selectedIndex); return returned; } return null; } private boolean completionListIsShown = false; private JComponent _completionList; void showCompletionList() { if (logger.isLoggable(Level.FINE)) { logger.fine("showCompletionList(), _popupIsShown=" + _owner.popupIsShown()); } if (logger.isLoggable(Level.FINE)) { logger.finest("showCompletionList() " + _completionListModel.getSize()); } if (!_owner.popupIsShown()) { _owner.openPopup(); } if (!completionListIsShown) { _lastBrowsed = _owner.getEditedObject(); if (_completionListModel.getSize() > 5) { scrollList = new JScrollPane(list); _completionList = scrollList; add(_completionList, BorderLayout.NORTH); list.setVisibleRowCount(5); scrollList.validate(); scrollList.repaint(); } else { _completionList = list; add(_completionList, BorderLayout.NORTH); } completionListIsShown = true; validate(); repaint(); } else { if (_completionList == list && _completionListModel.getSize() > 5 || _completionList == scrollList && _completionListModel.getSize() <= 5) { T rememberMe = _lastBrowsed; hideCompletionList(); showCompletionList(); _lastBrowsed = rememberMe; } } } void hideCompletionList() { _lastBrowsed = null; if (logger.isLoggable(Level.FINE)) { logger.finest("hideCompletionList()"); } if (completionListIsShown) { remove(_completionList); completionListIsShown = false; validate(); repaint(); } } protected void update() { if (_browser != null) { _browser.fireResetSelection(); if (_owner.getEditedObject() != null) { _browser.fireObjectSelected(_owner.getEditedObject()); } } } protected void delete() { _completionListModel.delete(); list.removeKeyListener(_owner.getCompletionListKeyAdapter()); list.removeListSelectionListener(listSelectionListener); list.removeMouseListener(listMouseListener); _browser.delete(); _browser = null; removeAll(); } protected class CompletionListModel extends AbstractListModel implements DocumentListener { protected int min; protected int max; private Vector data; private JTextField tf; public CompletionListModel(JTextField textField) { super(); tf = textField; min = 0; max = -1; } public void setData(Vector temp) { data = temp; min = 0; max = data.size() - 1; tf.getDocument().addDocumentListener(this); } public void delete() { if (tf != null) { tf.getDocument().removeDocumentListener(this); } data.clear(); min = 0; max = -1; } @Override public int getSize() { return max - min + 1; } @Override public Object getElementAt(int row) { try { return data.get(min + row); } catch (Exception e) { return null; } } public int getIndexOf(int row) { return min + row; } public String getCommonPrefix() { StringBuffer returned = new StringBuffer(); boolean stillMatches = true; int index = 0; String string1 = (String) data.get(min); String string2 = (String) data.get(max); while (stillMatches && index < string1.length() && index < string2.length()) { if (!string1.regionMatches(true, index, string2, index, 1)) { stillMatches = false; } else { if (index < tf.getText().length()) { returned.append(tf.getText().charAt(index)); } else { returned.append(string1.charAt(index)); } index++; } } return returned.toString(); } private void refreshInterval() { String curText = tf.getText(); int previousMin = min; int previousMax = max; min = 0; while (min < data.size() && !((String) data.get(min)).regionMatches(true, 0, curText, 0, curText.length())) { min++; } max = min; while (max < data.size() && ((String) data.get(max)).regionMatches(true, 0, curText, 0, curText.length())) { max++; } max--; if (previousMin < min) { fireIntervalRemoved(this, previousMin, min); } else if (previousMin > min) { fireIntervalAdded(this, min, previousMin); } if (previousMax < max) { fireIntervalAdded(this, previousMax, max); } else if (previousMax > max) { fireIntervalRemoved(this, max, previousMax); } } @Override public void changedUpdate(DocumentEvent arg0) { textWasChanged(); } @Override public void insertUpdate(DocumentEvent arg0) { textWasChanged(); } @Override public void removeUpdate(DocumentEvent arg0) { textWasChanged(); } protected void textWasChanged() { if (logger.isLoggable(Level.FINE)) { logger.fine("textWasChanged()"); } refreshInterval(); if (!AbstractSelectorPanel.this._owner.isProgrammaticalySet()) { if (logger.isLoggable(Level.FINE)) { logger.fine("showCompletionList()"); } showCompletionList(); } } } public ProjectBrowser getBrowser() { return _browser; } public void textWasChanged() { _completionListModel.textWasChanged(); } }