001: /* *************************************************************************
002:
003: Millstone(TM)
004: Open Sourced User Interface Library for
005: Internet Development with Java
006:
007: Millstone is a registered trademark of IT Mill Ltd
008: Copyright (C) 2000,2001,2002 IT Mill Ltd
009:
010: *************************************************************************
011:
012: This library is free software; you can redistribute it and/or
013: modify it under the terms of the GNU Lesser General Public
014: License as published by the Free Software Foundation; either
015: version 2.1 of the License, or (at your option) any later version.
016:
017: This library is distributed in the hope that it will be useful,
018: but WITHOUT ANY WARRANTY; without even the implied warranty of
019: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
020: Lesser General Public License for more details.
021:
022: You should have received a copy of the GNU Lesser General Public
023: License along with this library; if not, write to the Free Software
024: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
025:
026: *************************************************************************
027:
028: For more information, contact:
029:
030: IT Mill Ltd phone: +358 2 4802 7180
031: Ruukinkatu 2-4 fax: +358 2 4802 7181
032: 20540, Turku email: info@itmill.com
033: Finland company www: www.itmill.com
034:
035: Primary source for MillStone information and releases: www.millstone.org
036:
037: ********************************************************************** */
038:
039: package org.millstone.examples.features;
040:
041: import java.beans.BeanInfo;
042: import java.beans.IntrospectionException;
043: import java.beans.Introspector;
044: import java.beans.PropertyDescriptor;
045: import java.util.Date;
046: import java.util.HashSet;
047: import java.util.Iterator;
048: import java.util.LinkedList;
049:
050: import org.millstone.base.terminal.*;
051: import org.millstone.base.ui.*;
052: import org.millstone.base.data.*;
053: import org.millstone.base.data.util.*;
054:
055: public class PropertyPanel extends Panel implements
056: Button.ClickListener, Property.ValueChangeListener {
057:
058: private Select addComponent;
059: private OrderedLayout formsLayout = new OrderedLayout();
060: private LinkedList forms = new LinkedList();
061: private Button setButton = new Button("Set", this );
062: private Button discardButton = new Button("Discard changes", this );
063: private Button showAllProperties = new Button(
064: "List of All Properties", this );
065: private Table allProperties = new Table();
066: private Object objectToConfigure;
067: private BeanItem config;
068: protected static final int COLUMNS = 3;
069:
070: /** Contruct new property panel for configuring given object. */
071: public PropertyPanel(Object objectToConfigure) {
072: super ();
073:
074: // Layout
075: setCaption("Properties");
076: addComponent(formsLayout);
077:
078: // Target object
079: this .objectToConfigure = objectToConfigure;
080: config = new BeanItem(objectToConfigure);
081:
082: // Control buttons
083: OrderedLayout buttons = new OrderedLayout(
084: OrderedLayout.ORIENTATION_HORIZONTAL);
085: buttons.addComponent(setButton);
086: buttons.addComponent(discardButton);
087: addComponent(buttons);
088:
089: // Add default properties
090: addBasicComponentProperties();
091: if (objectToConfigure instanceof Select)
092: addSelectProperties();
093: if (objectToConfigure instanceof AbstractField
094: && !(objectToConfigure instanceof Table || objectToConfigure instanceof Tree))
095: addFieldProperties();
096: if ((objectToConfigure instanceof AbstractComponentContainer)
097: && !(objectToConfigure instanceof FrameWindow))
098: addComponentContainerProperties();
099:
100: // The list of all properties
101: addComponent(showAllProperties);
102: showAllProperties.setSwitchMode(true);
103: allProperties.setVisible(false);
104: allProperties.addContainerProperty("Name", String.class, "");
105: allProperties.addContainerProperty("Type", String.class, "");
106: allProperties.addContainerProperty("R/W", String.class, "");
107: allProperties.addContainerProperty("Demo", String.class, "");
108: allProperties.setColumnAlignments(new String[] {
109: Table.ALIGN_LEFT, Table.ALIGN_LEFT, Table.ALIGN_CENTER,
110: Table.ALIGN_CENTER });
111: allProperties.setColumnHeaderMode(Table.COLUMN_HEADER_MODE_ID);
112: updatePropertyList();
113: addComponent(allProperties);
114: }
115:
116: /** Add a formful of properties to property panel */
117: public void addProperties(String propertySetCaption, Form properties) {
118:
119: // Create new panel containing the form
120: Panel p = new Panel();
121: p.setWidth(600);
122: p.setCaption(propertySetCaption);
123: p.setStyle("light");
124: p.addComponent(properties);
125: formsLayout.addComponent(p);
126:
127: // Setup buffering
128: setButton.dependsOn(properties);
129: discardButton.dependsOn(properties);
130: properties.setWriteThrough(false);
131: properties.setReadThrough(true);
132:
133: // Maintain property lists
134: forms.add(properties);
135: updatePropertyList();
136: }
137:
138: /** Handle all button clicks for this panel */
139: public void buttonClick(Button.ClickEvent event) {
140:
141: // Commit all changed on all forms
142: if (event.getButton() == setButton) {
143: for (Iterator i = forms.iterator(); i.hasNext();)
144: ((Form) i.next()).commit();
145: }
146:
147: // Discard all changed on all forms
148: if (event.getButton() == discardButton) {
149: for (Iterator i = forms.iterator(); i.hasNext();)
150: ((Form) i.next()).discard();
151: }
152:
153: // Show property list
154: if (event.getButton() == showAllProperties) {
155: allProperties.setVisible(((Boolean) showAllProperties
156: .getValue()).booleanValue());
157: }
158: }
159:
160: /** Recreate property list contents */
161: public void updatePropertyList() {
162:
163: allProperties.removeAllItems();
164:
165: // Collect demoed properties
166: HashSet listed = new HashSet();
167: for (Iterator i = forms.iterator(); i.hasNext();)
168: listed.addAll(((Form) i.next()).getItemPropertyIds());
169:
170: // Resolve all properties
171: BeanInfo info;
172: try {
173: info = Introspector.getBeanInfo(objectToConfigure
174: .getClass());
175: } catch (IntrospectionException e) {
176: throw new RuntimeException(e.toString());
177: }
178: PropertyDescriptor[] pd = info.getPropertyDescriptors();
179:
180: // Fill the table
181: for (int i = 0; i < pd.length; i++) {
182: allProperties.addItem(new Object[] { pd[i].getName(),
183: pd[i].getPropertyType().getName(),
184: (pd[i].getWriteMethod() == null ? "R" : "R/W"),
185: (listed.contains(pd[i].getName()) ? "x" : "") },
186: pd[i]);
187: }
188: }
189:
190: /** Add basic properties implemented most often by abstract component */
191: private void addBasicComponentProperties() {
192:
193: // Set of properties
194: Form set = createBeanPropertySet(new String[] { "caption",
195: "icon", "componentError", "description", "enabled",
196: "visible", "style", "readOnly", "immediate" });
197:
198: // Icon
199: set.replaceWithSelect("icon", new Object[] { null,
200: new ThemeResource("icon/files/file.gif") },
201: new Object[] { "No icon", "Sample icon" });
202:
203: // Component error
204: Throwable sampleException;
205: try {
206: throw new NullPointerException("sample exception");
207: } catch (NullPointerException e) {
208: sampleException = e;
209: }
210: set
211: .replaceWithSelect(
212: "componentError",
213: new Object[] {
214: null,
215: new UserError(
216: "Sample text error message."),
217: new UserError(
218: "<h3>Error message formatting</h3><p>Error messages can "
219: + "contain any UIDL formatting, like: <ul><li><b>Bold"
220: + "</b></li><li><i>Italic</i></li></ul></p>",
221: UserError.CONTENT_UIDL,
222: ErrorMessage.INFORMATION),
223: new SystemError(
224: "This is an example of exception error reposting",
225: sampleException) },
226: new Object[] { "No error", "Sample text error",
227: "Sample Formatted error",
228: "Sample System Error" });
229:
230: // Style
231: set.replaceWithSelect("style", new Object[] { null },
232: new Object[] { "Default" }).setNewItemsAllowed(true);
233:
234: // Set up descriptions
235: set
236: .getField("caption")
237: .setDescription(
238: "Component caption is the title of the component. Usage of the caption is optional and the "
239: + "exact behavior of the propery is defined by the component. Setting caption null "
240: + "or empty disables the caption.");
241: set
242: .getField("enabled")
243: .setDescription(
244: "Enabled property controls the usage of the component. If the component is disabled (enabled=false),"
245: + " it can not receive any events from the terminal. In most cases it makes the usage"
246: + " of the component easier, if the component visually looks disbled (for example is grayed), "
247: + "when it can not be used.");
248: set
249: .getField("icon")
250: .setDescription(
251: "Icon of the component selects the main icon of the component. The usage of the icon is identical "
252: + "to caption and in most components caption and icon are kept together. Icons can be "
253: + "loaded from any resources (see Terminal/Resources for more information). Some components "
254: + "contain more than just the captions icon. Those icons are controlled through their "
255: + "own properties.");
256: set
257: .getField("visible")
258: .setDescription(
259: "Visibility property says if the component is renreded or not. Invisible components are implicitly "
260: + "disabled, as there is no visible user interface to send event.");
261: set
262: .getField("description")
263: .setDescription(
264: "Description is designed to allow easy addition of short tooltips, like this. Like the caption,"
265: + " setting description null or empty disables the description.");
266: set
267: .getField("readOnly")
268: .setDescription(
269: "Those components that have internal state that can be written are settable to readOnly-mode,"
270: + " where the object can only be read, not written.");
271: set
272: .getField("componentError")
273: .setDescription(
274: "Millstone supports extensive error reporting. One part of the error reporting are component"
275: + " errors that can be controlled by the programmer. This example only contains couple of "
276: + "sample errors; to get the full picture, read browse ErrorMessage-interface implementors "
277: + "API documentation.");
278: set
279: .getField("immediate")
280: .setDescription(
281: "Not all terminals can send the events immediately to server from all action. Web is the most "
282: + "typical environment where many events (like textfield changed) are not sent to server, "
283: + "before they are explicitly submitted. Setting immediate property true (by default this "
284: + "is false for most components), the programmer can assure that the application is"
285: + " notified as soon as possible about the value change in this component.");
286: set
287: .getField("style")
288: .setDescription(
289: "Themes specify the overall looks of the user interface. In addition component can have a set of "
290: + "styles, that can be visually very different (like datefield calendar- and text-styles), "
291: + "but contain the same logical functionality. As a rule of thumb, theme specifies if a "
292: + "component is blue or yellow and style determines how the component is used.");
293:
294: // Add created fields to property panel
295: addProperties("Component Basics", set);
296: }
297:
298: /** Add properties for selecting */
299: private void addSelectProperties() {
300: Form set = createBeanPropertySet(new String[] { "multiSelect",
301: "newItemsAllowed" });
302: addProperties("Select Properties", set);
303:
304: set.getField("multiSelect").setDescription(
305: "Specified if multiple items can be selected at once.");
306: set
307: .getField("newItemsAllowed")
308: .setDescription(
309: "Select component (but not Tree or Table) can allow the user to directly "
310: + "add new items to set of options. The new items are constrained to be "
311: + "strings and thus feature only applies to simple lists.");
312: if (objectToConfigure instanceof Tree
313: || objectToConfigure instanceof Table)
314: set.removeItemProperty("newItemsAllowed");
315: }
316:
317: /** Field special properties */
318: private void addFieldProperties() {
319: Form set = new Form(new GridLayout(COLUMNS, 1));
320: set.addField("focus", new Button("Focus", objectToConfigure,
321: "focus"));
322: set
323: .getField("focus")
324: .setDescription(
325: "Focus the cursor to this field. Not all "
326: + "components and/or terminals support this feature.");
327: addProperties("Field Features", set);
328: }
329:
330: /** Add and remove some miscellaneous example component to/from component container */
331: private void addComponentContainerProperties() {
332: Form set = new Form(new GridLayout(COLUMNS, 1));
333:
334: addComponent = new Select();
335: addComponent.setImmediate(true);
336: addComponent.addItem("Add component to container");
337: addComponent.setNullSelectionItemId("Add field");
338: addComponent.addItem("Text field");
339: addComponent.addItem("Time");
340: addComponent.addItem("Option group");
341: addComponent.addItem("Calendar");
342: addComponent.addListener(this );
343:
344: set.addField("component adder", addComponent);
345: set.addField("remove all components", new Button(
346: "Remove all components", objectToConfigure,
347: "removeAllComponents"));
348:
349: addProperties("ComponentContainer Features", set);
350: }
351:
352: /** Value change listener for listening selections */
353: public void valueChange(Property.ValueChangeEvent event) {
354:
355: // Adding components to component container
356: if (event.getProperty() == addComponent) {
357: String value = (String) addComponent.getValue();
358:
359: if (value != null) {
360: // TextField component
361: if (value.equals("Text field"))
362: ((AbstractComponentContainer) objectToConfigure)
363: .addComponent(new TextField("Test field"));
364:
365: // DateField time style
366: if (value.equals("Time")) {
367: DateField d = new DateField("Time", new Date());
368: d
369: .setDescription("This is a DateField-component with text-style");
370: d.setResolution(DateField.RESOLUTION_MIN);
371: d.setStyle("text");
372: ((AbstractComponentContainer) objectToConfigure)
373: .addComponent(d);
374: }
375:
376: // Date field calendar style
377: if (value.equals("Calendar")) {
378: DateField c = new DateField("Calendar", new Date());
379: c
380: .setDescription("DateField-component with calendar-style and day-resolution");
381: c.setStyle("calendar");
382: c.setResolution(DateField.RESOLUTION_DAY);
383: ((AbstractComponentContainer) objectToConfigure)
384: .addComponent(c);
385: }
386:
387: // Select option group style
388: if (value.equals("Option group")) {
389: Select s = new Select("Options");
390: s
391: .setDescription("Select-component with optiongroup-style");
392: s.addItem("Linux");
393: s.addItem("Windows");
394: s.addItem("Solaris");
395: s.addItem("Symbian");
396: s.setStyle("optiongroup");
397:
398: ((AbstractComponentContainer) objectToConfigure)
399: .addComponent(s);
400: }
401:
402: addComponent.setValue(null);
403: }
404: }
405: }
406:
407: /** Helper function for creating forms from array of propety names.
408: */
409: protected Form createBeanPropertySet(String names[]) {
410:
411: Form set = new Form(new GridLayout(COLUMNS, 1));
412:
413: for (int i = 0; i < names.length; i++) {
414: Property p = config.getItemProperty(names[i]);
415: if (p != null) {
416: set.addItemProperty(names[i], p);
417: Field f = set.getField(names[i]);
418: if (f instanceof TextField) {
419: if (Integer.class.equals(p.getType()))
420: ((TextField) f).setColumns(4);
421: else {
422: ((TextField) f).setNullSettingAllowed(true);
423: ((TextField) f).setColumns(24);
424: }
425: }
426: }
427: }
428:
429: return set;
430: }
431:
432: /** Find a field from all forms */
433: public Field getField(Object propertyId) {
434: for (Iterator i = forms.iterator(); i.hasNext();) {
435: Form f = (Form) i.next();
436: Field af = f.getField(propertyId);
437: if (af != null)
438: return af;
439: }
440: return null;
441: }
442: }
|