001: /*
002: * Beryl - A web platform based on XML, XSLT and Java
003: * This file is part of the Beryl XML GUI
004: *
005: * Copyright (C) 2004 Wenzel Jakob <wazlaf@tigris.org>
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011:
012: * This program is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this program; if not, write to the Free Software
019: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-3107 USA
020: */
021:
022: package org.beryl.gui.widgets;
023:
024: import java.awt.Component;
025: import java.awt.event.MouseAdapter;
026: import java.awt.event.MouseEvent;
027: import java.util.ArrayList;
028:
029: import javax.swing.DefaultListCellRenderer;
030: import javax.swing.DefaultListModel;
031: import javax.swing.JList;
032: import javax.swing.JScrollPane;
033: import javax.swing.ListModel;
034: import javax.swing.ListSelectionModel;
035: import javax.swing.event.ListDataEvent;
036: import javax.swing.event.ListDataListener;
037: import javax.swing.event.ListSelectionEvent;
038: import javax.swing.event.ListSelectionListener;
039:
040: import org.beryl.gui.GUIEvent;
041: import org.beryl.gui.GUIEventListener;
042: import org.beryl.gui.GUIException;
043: import org.beryl.gui.Widget;
044: import org.beryl.gui.WidgetInfo;
045: import org.beryl.gui.model.ListChangeEvent;
046: import org.beryl.gui.model.ListDataModel;
047: import org.beryl.gui.model.MapChangeEvent;
048: import org.beryl.gui.model.MapDataModel;
049: import org.beryl.gui.model.ModelChangeEvent;
050: import org.beryl.gui.model.ModelChangeListener;
051:
052: public class List extends Widget {
053: private static WidgetInfo listInfo = null;
054: private JList list = null;
055: private JScrollPane scrollPane = null;
056: private ListDataModel listDataModel = null;
057: private String indexKey, valueKey = null;
058: private boolean sendEvents = true;
059: private boolean processEvents = true;
060: private boolean isConstructed = false;
061:
062: static {
063: listInfo = new WidgetInfo(List.class, widgetInfo);
064: listInfo.addProperty("valuekey", "string", "");
065: listInfo.addProperty("indexkey", "string", "");
066: listInfo
067: .addProperty("verticalScrollBar", "bool", Boolean.FALSE);
068: listInfo.addProperty("horizontalScrollBar", "bool",
069: Boolean.FALSE);
070: listInfo.addProperty("selectionMode", "enum",
071: "multiple_interval");
072: listInfo.addEvent("rightclick");
073: listInfo.addEvent("doubleclick");
074: };
075:
076: public static class ListDataModelAdapter implements ListModel,
077: ModelChangeListener {
078: private ListDataModel model = null;
079: private ArrayList listeners = null;
080:
081: public ListDataModelAdapter(ListDataModel model) {
082: this .model = model;
083: model.addModelChangeListener(this );
084: listeners = new ArrayList();
085: }
086:
087: public int getSize() {
088: return model.getSize();
089: }
090:
091: public Object getElementAt(int index) {
092: return model.getValue(index);
093: }
094:
095: public void addListDataListener(ListDataListener l) {
096: listeners.add(l);
097: }
098:
099: public void removeListDataListener(ListDataListener l) {
100: listeners.remove(l);
101: }
102:
103: public void modelChanged(ModelChangeEvent e) {
104: ListChangeEvent lce = (ListChangeEvent) e;
105: ListDataEvent event = new ListDataEvent(e.getSource(), lce
106: .getType(), lce.getFirstIndex(), lce.getLastIndex());
107:
108: for (int i = 0; i < listeners.size(); i++) {
109: ((ListDataListener) listeners.get(i))
110: .contentsChanged(event);
111: }
112: }
113: };
114:
115: public class ListCellRenderer extends DefaultListCellRenderer {
116: public Component getListCellRendererComponent(JList list,
117: Object value, int index, boolean sel, boolean hasFocus) {
118: super .getListCellRendererComponent(list, value, index, sel,
119: hasFocus);
120: try {
121: Item node = (Item) value;
122: if (node.getIcon() != null) {
123: setIcon(node.getIcon());
124: }
125: } catch (ClassCastException e) {
126: /* Ignore */
127: }
128: return this ;
129: }
130: }
131:
132: public List(Widget parent, String name) throws GUIException {
133: super (parent, name);
134: list = new JList();
135: list.setCellRenderer(new ListCellRenderer());
136: scrollPane = new JScrollPane(list);
137: list.addListSelectionListener(new ListSelectionListener() {
138: public void valueChanged(ListSelectionEvent e) {
139: try {
140: MapDataModel model = getDataModel();
141: if (sendEvents && model != null
142: && !e.getValueIsAdjusting()) {
143: try {
144: sendEvents = false;
145: processEvents = false;
146: if (indexKey != null)
147: model.setValue(List.this , indexKey,
148: list.getSelectedIndices());
149: if (valueKey != null)
150: model.setValue(List.this , valueKey,
151: list.getSelectedValues());
152: } finally {
153: sendEvents = true;
154: processEvents = true;
155: }
156: }
157: } catch (GUIException ex) {
158: throw new RuntimeException(ex);
159: }
160: }
161: });
162: list
163: .setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
164: }
165:
166: public void addChild(Widget widget, Object constraint)
167: throws GUIException {
168: if (widget instanceof Item) {
169: try {
170: if (listDataModel == null) {
171: listDataModel = new ListDataModel();
172: sendEvents = false;
173: list.setModel(new ListDataModelAdapter(
174: listDataModel));
175: }
176: listDataModel.addValue(this , widget);
177: addChild(widget);
178: } finally {
179: sendEvents = true;
180: }
181: } else {
182: throw new GUIException(
183: "Only Item children are allowed inside a List");
184: }
185: }
186:
187: public void removeChildWidget(Widget widget) throws GUIException {
188: if (listDataModel != null) {
189: listDataModel.removeValue(this , widget);
190: super .removeChildWidget(widget);
191: } else {
192: throw new GUIException(
193: "There are no static items to remove");
194: }
195: }
196:
197: public void setProperty(String name, Object value)
198: throws GUIException {
199: if (name.startsWith("item.")) {
200: ListModel model = list.getModel();
201: if (!(model instanceof DefaultListModel))
202: list.setModel(new DefaultListModel());
203: ((DefaultListModel) model).addElement(value);
204: } else if ("indexkey".equals(name)) {
205: indexKey = (String) value;
206: } else if ("valuekey".equals(name)) {
207: valueKey = (String) value;
208: } else if ("verticalScrollBar".equals(name)) {
209: scrollPane
210: .setVerticalScrollBarPolicy(((Boolean) value)
211: .booleanValue() ? JScrollPane.VERTICAL_SCROLLBAR_ALWAYS
212: : JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
213: } else if ("horizontalScrollBar".equals(name)) {
214: scrollPane
215: .setHorizontalScrollBarPolicy(((Boolean) value)
216: .booleanValue() ? JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS
217: : JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
218: } else {
219: super .setProperty(name, value);
220: }
221: }
222:
223: public void setListDataModel(ListDataModel listDataModel)
224: throws GUIException {
225: ModelChangeEvent event = new ModelChangeEvent(this ,
226: listDataModel);
227: this .listDataModel = listDataModel;
228: sendEvents = false;
229: try {
230: list.setModel(new ListDataModelAdapter(listDataModel));
231: } finally {
232: sendEvents = true;
233: }
234: /* Reload data model information */
235: modelChanged(event);
236: }
237:
238: public ListDataModel getListDataModel() {
239: return this .listDataModel;
240: }
241:
242: private void setSelectionIndices(int[] indices) {
243: if (indices == null) {
244: list.clearSelection();
245: } else {
246: list.clearSelection();
247: for (int i = 0; i < indices.length; i++) {
248: int index = indices[i];
249: list.addSelectionInterval(index, index);
250: list.ensureIndexIsVisible(index);
251: }
252: }
253: }
254:
255: private void setSelectionValues(Object[] values) {
256: if (values == null) {
257: list.clearSelection();
258: } else {
259: list.clearSelection();
260: for (int i = 0; i < values.length; i++) {
261: int index = listDataModel.indexOf(values[i]);
262: list.addSelectionInterval(index, index);
263: list.ensureIndexIsVisible(index);
264: }
265: }
266: }
267:
268: private void reload() throws GUIException {
269: MapDataModel model = getDataModel();
270: if (model != null) {
271: try {
272: processEvents = false;
273:
274: int[] indices = indexKey == null ? null : (int[]) model
275: .getValue(indexKey);
276: Object values[] = valueKey == null ? null
277: : (Object[]) model.getValue(valueKey);
278:
279: if (indices != null) {
280: setSelectionIndices(indices);
281: } else if (values != null) {
282: setSelectionValues(values);
283: }
284:
285: if (((values != null && indices == null) || (values == null && indices == null))
286: && indexKey != null) {
287: model.setValue(List.this , indexKey, list
288: .getSelectedIndices());
289: }
290:
291: if (((indices != null && values == null) || (values == null && indices == null))
292: && valueKey != null) {
293: model.setValue(List.this , valueKey, list
294: .getSelectedValues());
295: }
296: } finally {
297: processEvents = true;
298: }
299: }
300: }
301:
302: public void modelChanged(ModelChangeEvent e) throws GUIException {
303: if (processEvents) {
304: try {
305: sendEvents = false;
306: if (e.getSource() == this ) {
307: try {
308: reload();
309: } catch (IllegalArgumentException ex) {
310: /* Ignore, list data model is not yet set */
311: } catch (ArrayIndexOutOfBoundsException ex) {
312: /* Ignore, list data model is not yet set */
313: }
314: } else if (e instanceof MapChangeEvent) {
315: MapChangeEvent event = (MapChangeEvent) e;
316: if (event.getKey() == null) {
317: reload();
318: } else if (event.getKey().equals(indexKey)) {
319: setSelectionIndices((int[]) event.getNewValue());
320: try {
321: processEvents = false;
322: if (valueKey != null)
323: ((MapDataModel) event.getModel())
324: .setValue(
325: List.this ,
326: valueKey,
327: list
328: .getSelectedValues());
329: } finally {
330: processEvents = true;
331: }
332: } else if (event.getKey().equals(valueKey)) {
333: setSelectionValues((Object[]) event
334: .getNewValue());
335: try {
336: processEvents = false;
337: if (indexKey != null)
338: ((MapDataModel) event.getModel())
339: .setValue(
340: List.this ,
341: indexKey,
342: list
343: .getSelectedIndices());
344: } finally {
345: processEvents = true;
346: }
347: }
348: }
349: } finally {
350: sendEvents = true;
351: }
352: }
353: }
354:
355: public void addListener(String event, final String name,
356: final GUIEventListener listener) throws GUIException {
357: if ("rightclick".equals(event)) {
358: list.addMouseListener(new MouseAdapter() {
359: public void mousePressed(MouseEvent me) {
360: int row = list.locationToIndex(me.getPoint());
361: boolean multipleSelected = list
362: .getSelectedIndices().length > 1;
363: boolean rowSelected = list.isSelectedIndex(row);
364:
365: if (me.isPopupTrigger()
366: && (multipleSelected || rowSelected)) {
367: listener.eventOccured(new GUIEvent(List.this ,
368: name, me));
369: }
370: }
371:
372: public void mouseReleased(MouseEvent me) {
373: mousePressed(me);
374: }
375: });
376: } else if (event.equals("doubleclick")) {
377: list.addMouseListener(new MouseAdapter() {
378: public void mousePressed(MouseEvent me) {
379: if (me.getClickCount() == 2) {
380: listener.eventOccured(new GUIEvent(List.this ,
381: name, me));
382: }
383: }
384: });
385: } else {
386: super .addListener(event, name, listener);
387: }
388: }
389:
390: public void finalizeConstruction() throws GUIException {
391: isConstructed = true;
392: super .finalizeConstruction();
393: }
394:
395: public Component getWidget() {
396: return scrollPane;
397: }
398:
399: public Component getRealWidget() {
400: return list;
401: }
402:
403: public WidgetInfo getWidgetInfo() {
404: return listInfo;
405: }
406: }
|