001: package org.enhydra.jawe;
002:
003: import java.awt.Color;
004: import java.awt.Component;
005: import java.awt.Dimension;
006: import java.awt.Font;
007: import java.awt.Insets;
008: import java.awt.event.ActionEvent;
009: import java.awt.event.ActionListener;
010: import java.awt.event.KeyAdapter;
011: import java.awt.event.KeyEvent;
012: import java.awt.event.MouseAdapter;
013: import java.awt.event.MouseEvent;
014: import java.util.ArrayList;
015: import java.util.Iterator;
016: import java.util.List;
017: import java.util.Map;
018:
019: import javax.swing.AbstractListModel;
020: import javax.swing.BorderFactory;
021: import javax.swing.Icon;
022: import javax.swing.ImageIcon;
023: import javax.swing.JLabel;
024: import javax.swing.JList;
025: import javax.swing.JMenuItem;
026: import javax.swing.JPopupMenu;
027: import javax.swing.JScrollPane;
028: import javax.swing.JTextField;
029: import javax.swing.ListCellRenderer;
030: import javax.swing.event.DocumentEvent;
031: import javax.swing.event.DocumentListener;
032: import javax.swing.text.BadLocationException;
033: import javax.swing.text.Document;
034:
035: import org.enhydra.shark.utilities.SequencedHashMap;
036: import org.enhydra.shark.xpdl.XMLComplexElement;
037: import org.enhydra.shark.xpdl.XMLElement;
038:
039: /**
040: * Creates button witch displays popup with available choices for showing xpdl objects.
041: * Added functionnalities to automatically support
042: *
043: * @author Sasa Bojanic
044: * @author Miroslav Popov
045: */
046: public class XMLElementChoiceButton extends ChoiceButton implements
047: ActionListener {
048: private static int MODE_NONE = 0;
049: private static int MODE_LONG_LIST = 1;
050: private static int MODE_SHORT_LIST = 2;
051: private static int MIN_LIST_SIZE = 30;
052:
053: protected ChoiceButtonListener parent;
054: protected int lastMode = MODE_NONE;
055: protected SequencedHashMap choiceMap = new SequencedHashMap();
056: protected JPopupMenu popup = new JPopupMenu();
057:
058: protected JTextField filterField = new JTextField();
059: protected FilteredListModel filterModel = new FilteredListModel();
060: protected JList longList;
061:
062: protected Class choiceType;
063: protected String[] filterDatas;
064:
065: protected ImageIcon defIcon = null;
066:
067: protected boolean alwaysUseDefaultIcon = true;
068:
069: protected static Icon select;
070:
071: static {
072: try {
073: select = new ImageIcon(
074: XMLElementChoiceButton.class
075: .getClassLoader()
076: .getResource(
077: "org/enhydra/jawe/images/select_icon_small.gif"));
078: } catch (Exception e) {
079: }
080: }
081:
082: public XMLElementChoiceButton(Class choiceType,
083: ChoiceButtonListener parent, ImageIcon icon,
084: boolean alwaysUseDefaultIcon) {
085: this (choiceType, parent, icon, alwaysUseDefaultIcon, null);
086: }
087:
088: /**
089: * Create a choice button. If filterDatas is not null, this is is a list of element childs
090: * names that will be used to filter.
091: * @param choiceType
092: * @param parent
093: * @param icon
094: * @param filterDatas
095: */
096: public XMLElementChoiceButton(Class choiceType,
097: ChoiceButtonListener parent, ImageIcon icon,
098: boolean alwaysUseDefaultIcon, String[] filterDatas) {
099: this .parent = parent;
100: this .choiceType = choiceType;
101: this .filterDatas = filterDatas;
102:
103: this .defIcon = icon;
104: this .alwaysUseDefaultIcon = alwaysUseDefaultIcon;
105: setIcon(icon);
106: addActionListener(this );
107: setMargin(new Insets(1, 2, 1, 2));
108: setSize(new Dimension(10, 8));
109: setAlignmentY(0.5f);
110:
111: longList = new JList(filterModel) {
112: public String getToolTipText(MouseEvent evt) {
113: int index = locationToIndex(evt.getPoint());
114: Object item = getModel().getElementAt(index);
115: return getElementTooltipText((XMLElement) ((Map.Entry) item)
116: .getValue());
117: }
118: };
119: filterModel.setFilteredList(choiceMap.entrySet().toArray());
120: filterField.getDocument().addDocumentListener(filterModel);
121: filterField.addActionListener(filterModel);
122: longList.setCellRenderer(new ChoiceButtonCellRenderer());
123: longList.setVisibleRowCount(MIN_LIST_SIZE);
124: longList.addMouseListener(new MouseAdapter() {
125: public void mouseClicked(MouseEvent e) {
126: if ((e.getSource() == longList)) {
127: int item = longList.locationToIndex(e.getPoint());
128: if (item >= 0)
129: setSelectedItem(((Map.Entry) filterModel
130: .getElementAt(item)).getValue());
131: }
132:
133: }
134: });
135: longList.addKeyListener(new KeyAdapter() {
136: public void keyReleased(KeyEvent ke) {
137: Object o = longList.getSelectedValue();
138: if ((ke.getKeyCode() == KeyEvent.VK_ENTER)
139: && (o != null))
140: setSelectedItem(((Map.Entry) o).getValue());
141: }
142: });
143: }
144:
145: public void actionPerformed(ActionEvent ae) {
146: if (ae.getSource() == this ) {
147: refresh();
148: if (choiceMap.size() > 0) {
149: popup.show(this .getParent(), this .getX(), this .getY()
150: + this .getHeight());
151: filterField.grabFocus();
152: }
153: } else {
154: JMenuItem selected = (JMenuItem) ae.getSource();
155: int sel = popup.getComponentIndex(selected);
156: Object obj = choiceMap.getValue(sel);
157: setSelectedItem(obj);
158: }
159:
160: }
161:
162: private void setSelectedItem(Object obj) {
163:
164: parent.selectionChanged(this , obj);
165: choiceMap.clear();
166: popup.setVisible(false);
167: setObjectIcon(obj);
168: }
169:
170: public void setObjectIcon(Object obj) {
171: if (obj instanceof XMLElement && !alwaysUseDefaultIcon) {
172: ImageIcon ic = JaWEManager.getInstance()
173: .getJaWEController().getTypeResolver().getJaWEType(
174: (XMLElement) obj).getIcon();
175: if (ic == null) {
176: ic = defIcon;
177: } else {
178: if (select != null) {
179: ic = new CombinedIcon(ic, select,
180: CombinedIcon.POS_BOTTOM_RIGHT);
181: }
182: }
183: setIcon(ic);
184: }
185: }
186:
187: protected void refresh() {
188: choiceMap.clear();
189: List choices = parent.getChoices(this );
190: if (choices != null) {
191: Iterator it = choices.iterator();
192: while (it.hasNext()) {
193: XMLElement choice = (XMLElement) it.next();
194: String dispName = " "
195: + JaWEManager.getInstance()
196: .getDisplayNameGenerator()
197: .getDisplayName(choice) + " ";
198: if (choiceMap.containsKey(dispName)) {
199: if (choice instanceof XMLComplexElement) {
200: XMLElement idEl = ((XMLComplexElement) choice)
201: .get("Id");
202: dispName += "["
203: + ResourceManager
204: .getLanguageDependentString("IdKey")
205: + "=" + idEl.toValue() + "] ";
206: }
207: }
208: choiceMap.put(dispName, choice);
209: }
210: }
211: if (choiceMap.size() >= MIN_LIST_SIZE) {
212: filterModel.setFilteredList(choiceMap.entrySet().toArray());
213: if (lastMode != MODE_LONG_LIST) {
214: popup.removeAll();
215: filterField.setText("");
216: JScrollPane p = new JScrollPane(longList);
217: popup.add(filterField);
218: popup.add(p);
219: }
220: lastMode = MODE_LONG_LIST;
221: } else if (choiceMap.size() > 0) {
222: popup.removeAll();
223: String[] names = new String[choiceMap.size()];
224: choiceMap.keySet().toArray(names);
225: for (int i = 0; i < choiceMap.size(); i++) {
226: JMenuItem mi = new JMenuItem(names[i]);
227: mi
228: .setToolTipText(getElementTooltipText((XMLElement) choiceMap
229: .get(names[i])));
230: popup.add(mi);
231: mi.addActionListener(this );
232: }
233: lastMode = MODE_SHORT_LIST;
234: }
235: }
236:
237: public Class getChoiceType() {
238: return choiceType;
239: }
240:
241: private String getElementTooltipText(XMLElement element) {
242: return JaWEManager.getInstance().getTooltipGenerator()
243: .getTooltip(element);
244: }
245:
246: private class ChoiceButtonCellRenderer extends JLabel implements
247: ListCellRenderer {
248: Font bold;
249: Font normal;
250:
251: public ChoiceButtonCellRenderer() {
252: Font f = getFont();
253: bold = f.deriveFont(f.getStyle() | Font.BOLD);
254: normal = f.deriveFont(f.getStyle() & (~Font.BOLD));
255:
256: }
257:
258: /**
259: * returns the cell renderer use for the JList
260: */
261: public Component getListCellRendererComponent(JList list,
262: Object value, int index, boolean isSelected,
263: boolean cellHasFocus) {
264: String key = ((Map.Entry) value).getKey().toString();
265: setText(key);
266: setOpaque(true);
267: /* put a border on top of first 'out of selection' ite and around selected items */
268: if (isSelected)
269: setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1,
270: Color.BLACK));
271: else if ((index == filterModel.getFilterSize())
272: && (index > 0))
273: setBorder(BorderFactory.createMatteBorder(1, 0, 0, 0,
274: Color.red));
275: else
276: setBorder(null);
277: /* set foreground/background depending on wether elements are selected and wether
278: * or not they are in filter
279: */
280: if ((index >= filterModel.getFilterSize())
281: || (filterModel.getFilterSize() == filterModel
282: .getSize())) {
283: if (isSelected) {
284: setBackground(list.getForeground());
285: setForeground(list.getBackground());
286: } else {
287: setBackground(list.getBackground());
288: setForeground(list.getForeground());
289: }
290: } else {
291: if (isSelected) {
292: setBackground(list.getSelectionForeground());
293: setForeground(list.getSelectionBackground());
294: } else {
295: setBackground(list.getSelectionBackground());
296: setForeground(list.getSelectionForeground());
297: }
298: }
299: /* set bold to mark selected elements */
300: if (isSelected)
301: setFont(bold);
302: else
303: setFont(normal);
304:
305: return this ;
306: }
307: }
308:
309: /** This class is the core junction between the list of element,
310: * the filter textfield and the rendering of JList component*/
311: private class FilteredListModel extends AbstractListModel implements
312: DocumentListener, ActionListener {
313:
314: private ArrayList fullList = new ArrayList();
315: private ArrayList filteredList = new ArrayList();
316: private ArrayList rejectedList = new ArrayList();
317: private String key;
318:
319: /**
320: * This is mainly for convenience with output from map.entrySet.toArray() that
321: * this method accept an Object[] parameter. The content of array should be Map.Entry
322: * where key is a String and value is an XMLElement
323: *
324: * @param list the list of element to filter from in this model
325: */
326: public void setFilteredList(Object[] list) {
327: fullList.clear();
328: filteredList.clear();
329: for (int i = 0; i < list.length; i++)
330: fullList.add((Map.Entry) list[i]);
331: filteredList.addAll(fullList);
332: if (key != null)
333: filter(key);
334: }
335:
336: public int getFilterSize() {
337: return filteredList.size();
338: }
339:
340: public int getSize() {
341: return fullList.size();
342: }
343:
344: public Object getElementAt(int index) {
345: if (index >= 0) {
346: if (index < filteredList.size()) {
347: return filteredList.get(index);
348: } else if (index < fullList.size()) {
349: return rejectedList
350: .get(index - filteredList.size());
351: }
352: }
353: return null;
354: }
355:
356: private boolean match(String key, XMLElement element) {
357: if ((filterDatas != null) && (filterDatas.length > 0)
358: && (element instanceof XMLComplexElement)) {
359: XMLComplexElement cmel = (XMLComplexElement) element;
360: for (int i = 0; i < filterDatas.length; i++) {
361: XMLElement el = cmel.get(filterDatas[i].toString());
362: if (el != null) {
363: String value = el.toValue();
364: if ((value != null)
365: && (value.toLowerCase().indexOf(key) >= 0))
366: return true;
367: }
368: }
369: }
370:
371: String value = element.toValue();
372: if ((value != null)
373: && (value.toLowerCase().indexOf(key) >= 0))
374: return true;
375:
376: return false;
377: }
378:
379: private boolean splitAndMatch(String key, XMLElement element) {
380: key = key.toLowerCase().trim();
381: String[] elements = key.split("\\s");
382: for (int i = 0; i < elements.length; i++)
383: if (!match(elements[i], element))
384: return false;
385: return (key.length() > 0);
386: }
387:
388: /**
389: * updates model according to filter key
390: */
391: public void filter(String key) {
392: this .key = key;
393: filteredList.clear();
394: rejectedList.clear();
395: for (Iterator i = fullList.iterator(); i.hasNext();) {
396: Map.Entry entry = (Map.Entry) i.next();
397: // String name= entry.getKey().toString();
398: XMLElement element = (XMLElement) entry.getValue();
399: if (splitAndMatch(key, element))
400: filteredList.add(entry);
401: else
402: rejectedList.add(entry);
403: }
404: fireContentsChanged(this , 0, getSize());
405: }
406:
407: /* Manage changes in textfield */
408: private void updateFilter(Document doc) {
409: try {
410: String key;
411: key = doc.getText(0, doc.getLength());
412: filter(key);
413: } catch (BadLocationException e1) {
414: e1.printStackTrace();
415: }
416: }
417:
418: /** Do nothing */
419: public void changedUpdate(DocumentEvent e) {
420: // nothing to do
421: }
422:
423: /**
424: * Handles updates in Textfield, requires to redo filtering and updates filter accordingly
425: * @param e DocumentEvent
426: */
427: public void insertUpdate(DocumentEvent e) {
428: updateFilter(e.getDocument());
429: }
430:
431: /**
432: * Handles updates in Textfield, requires to redo filtering and updates filter accordingly
433: * @param e DocumentEvent
434: */
435: public void removeUpdate(DocumentEvent e) {
436: updateFilter(e.getDocument());
437: }
438:
439: /* manage actions in texfield */
440:
441: /** triggered when action is performed in TextField.
442: * This mean a 'enter' from textfield and we select a specific elt in
443: * JList if there is only one match if filtering
444: */
445: public void actionPerformed(ActionEvent e) {
446: if (filteredList.size() == 1) {
447: longList.setSelectedIndex(0);
448: }
449: }
450:
451: }
452: }
|