001: package jimm.datavision.field;
002:
003: import jimm.datavision.*;
004: import jimm.datavision.gui.FieldWidget;
005: import jimm.datavision.gui.SectionWidget;
006: import jimm.util.I18N;
007: import jimm.util.XMLWriter;
008: import java.util.Observable;
009:
010: /**
011: * The abstract superclass of visual report fields that display text labels,
012: * database columns, special values, aggregate values, formulas, and
013: * parameters. A field has a bounds {@link Rectangle} that determines its
014: * position within a section and an associated {@link Format} and {@link
015: * Border} for determining how to display the field.
016: * <p>
017: * To avoid repeated font size and line width calculations, a {@link
018: * FormattedValueCache} holds the formatted version of this field's value.
019: *
020: * @author Jim Menard, <a href="mailto:jimm@io.com">jimm@io.com</a>
021: */
022: public abstract class Field extends Element implements Identity,
023: Draggable, Cloneable {
024:
025: public static final double DEFAULT_WIDTH = 120;
026: public static final double DEFAULT_HEIGHT = 16;
027:
028: static Long maxIdSeen = new Long(0);
029:
030: protected Long id;
031: protected Rectangle bounds;
032: protected Format format;
033: protected Border border; // Possibly null
034: protected Object value; // String or id (column, formula, etc.)
035: protected FormattedValueCache cache;
036:
037: /**
038: * This factory method constructs and returns a new instance of a subclass
039: * of <code>Field</code> based on the <var>type</var> string.
040: * <p>
041: * If <var>id</var> is <code>null</code>, generates a new id number. This
042: * number is one higher than any previously-seen id number. This does
043: * <em>not</em> guarantee that no later field will be created manually with
044: * the same id number.
045: *
046: * @param id the unique identifier for the new field; if <code>null</code>,
047: * generate a new id
048: * @param section the report section containing the field
049: * @param type one of "special", "text", "column", "formula", "parameter",
050: * "image", or one of the aggregate function names; found in report XML
051: */
052: public static Field create(Long id, Report report, Section section,
053: String type, Object value, boolean visible) {
054: if (type == null || type.length() == 0)
055: throw new IllegalArgumentException(I18N
056: .get("Field.field_cap")
057: + " " + id + ": " + I18N.get("Field.need_type"));
058:
059: type = type.toLowerCase();
060: if (type.equals("special"))
061: return new SpecialField(id, report, section, value, visible);
062: else if (type.equals("text"))
063: return new TextField(id, report, section, value, visible);
064: else if (type.equals("column"))
065: return new ColumnField(id, report, section, value, visible);
066: else if (type.equals("formula"))
067: return new FormulaField(id, report, section, value, visible);
068: else if (type.equals("parameter"))
069: return new ParameterField(id, report, section, value,
070: visible);
071: else if (type.equals("image"))
072: return new ImageField(id, report, section, value, visible);
073: else if (type.equals("usercol"))
074: return new UserColumnField(id, report, section, value,
075: visible);
076: else if (type.equals("subreport"))
077: return new SubreportField(id, report, section, value,
078: visible);
079: else if (AggregateField.isAggregateFunctionName(type))
080: return new AggregateField(id, report, section, value,
081: visible, type);
082:
083: throw new IllegalArgumentException(I18N.get("Field.field_cap")
084: + " " + id + ": " + I18N.get("Field.unknown") + " \""
085: + type + "\"");
086: }
087:
088: /**
089: * Creates a field from a drag string. <var>str</var> should a string
090: * created by some field's {@link #dragString} method.
091: *
092: * @param report the report containing this element
093: * @param str a drag string
094: * @return a new field
095: */
096: public static Field createFromDragString(Report report, String str) {
097: int pos = str.indexOf(":");
098: if (pos == -1)
099: return null;
100:
101: String type = str.substring(0, pos);
102: String value = str.substring(pos + 1);
103: return create(null, report, null, type, value, true);
104: }
105:
106: /**
107: * Constructor.
108: *
109: * @param id the unique identifier for the new field
110: * @param report the report containing this element
111: * @param section the report section containing the field
112: * @param value the value this field represents visually
113: * @param visible show/hide flag
114: */
115: protected Field(Long id, Report report, Section section,
116: Object value, boolean visible) {
117: super (report, section, visible);
118:
119: if (id == null) // Generate new value
120: id = new Long(maxIdSeen.longValue() + 1);
121: if (id.compareTo(maxIdSeen) == 1)
122: maxIdSeen = new Long(id.longValue());
123: this .id = id;
124:
125: format = Format.createEmptyFormat();
126: format.setField(this );
127: format.addObserver(this );
128:
129: // The defaultField will be null only when we are creating the
130: // default field itself.
131: Field defaultField = report.getDefaultField();
132:
133: this .value = value;
134: cache = new FormattedValueCache(this );
135: bounds = defaultField != null ? new Rectangle(0, 0,
136: defaultField.getBounds().width, defaultField
137: .getBounds().height) : new Rectangle(0, 0,
138: DEFAULT_WIDTH, DEFAULT_HEIGHT);
139: bounds.addObserver(this );
140:
141: // Make sure changes to the default field are reflected in the GUI
142: if (defaultField != null)
143: defaultField.addObserver(this );
144: }
145:
146: /**
147: * Returns a clone. Subclasses may need ot override this method to copy
148: * additional instance variables that are not set in their constructors.
149: *
150: * @return an almost-ready clone of this object
151: */
152: public Object clone() {
153: Field f = Field.create(null, report, section, typeString(),
154: value, true);
155: f.bounds = new Rectangle(bounds);
156: f.format = (Format) format.clone();
157: f.format.setField(f);
158: if (border == null)
159: f.border = null;
160: else {
161: f.border = (Border) border.clone();
162: f.border.setField(f);
163: }
164: return f;
165: }
166:
167: protected void finalize() throws Throwable {
168: bounds.deleteObserver(this );
169: if (format != null)
170: format.deleteObserver(this );
171: if (border != null)
172: border.deleteObserver(this );
173: }
174:
175: public void update(Observable o, Object arg) {
176: super .update(o, arg);
177: if (format != null)
178: format.clearFontCache();
179: }
180:
181: public Object getId() {
182: return id;
183: }
184:
185: /**
186: * Returns the bounds rectangle for this field.
187: *
188: * @return the bounds rectangle
189: */
190: public Rectangle getBounds() {
191: return bounds;
192: }
193:
194: /**
195: * Sets the bounds rectangle.
196: *
197: * @param newBounds the new bounds rectangle
198: */
199: public void setBounds(Rectangle newBounds) {
200: if (bounds != newBounds) {
201: bounds.deleteObserver(this );
202: bounds = newBounds;
203: bounds.addObserver(this );
204: setChanged();
205: notifyObservers();
206: }
207: }
208:
209: /**
210: * Returns the height needed to output the current value of this field.
211: * This default implementation returns the height of the field as
212: * defined in the report designer (the bounds height).
213: */
214: public double getOutputHeight() {
215: return cache.getOutputHeight(getValue());
216: }
217:
218: /**
219: * Returns the format for this field. May return <code>null</code>.
220: *
221: * @return the format, possibly <code>null</code>
222: */
223: public Format getFormat() {
224: return format;
225: }
226:
227: /**
228: * Sets the format. If this field already has a format, you can just modify
229: * it instead of giving it a completely new one.
230: *
231: * @param newFormat the format
232: */
233: public void setFormat(Format newFormat) {
234: if (format != newFormat) {
235: if (format != null)
236: format.deleteObserver(this );
237: format = newFormat;
238: format.setField(this );
239: if (format != null)
240: format.addObserver(this );
241: setChanged();
242: notifyObservers();
243: }
244: }
245:
246: /**
247: * Returns the border for this field. May return <code>null</code>.
248: *
249: * @return the border, possibly <code>null</code>
250: */
251: public Border getBorder() {
252: return border;
253: }
254:
255: /**
256: * Returns the border for this field or, if it is <code>null</code>, the
257: * report's default border. If we return the default border, we clone it
258: * in order to give it this field.
259: *
260: * @return this field's border or the default border
261: */
262: public Border getBorderOrDefault() {
263: if (border != null)
264: return border;
265:
266: Border b = (Border) report.getDefaultField().getBorder()
267: .clone();
268: b.setField(this );
269: return b;
270: }
271:
272: /**
273: * Sets the border.
274: *
275: * @param newBorder the new border
276: */
277: public void setBorder(Border newBorder) {
278: if (border != newBorder) {
279: if (border != null)
280: border.deleteObserver(this );
281: border = newBorder;
282: if (border != null)
283: border.addObserver(this );
284: setChanged();
285: notifyObservers();
286: }
287: }
288:
289: /**
290: * Returns the value for this field. May return <code>null</code>.
291: *
292: * @return the value, possibly <code>null</code>
293: */
294: public Object getValue() {
295: return value;
296: }
297:
298: /**
299: * Sets the value.
300: *
301: * @param newValue the new value
302: */
303: public void setValue(Object newValue) {
304: if (value != newValue) {
305: value = newValue;
306: setChanged();
307: notifyObservers();
308: }
309: }
310:
311: /**
312: * Returns a new widget of the appropriate <code>FieldWidget</code>
313: * subclass for this field. Subclasses override this method to return
314: * different types of widgets.
315: *
316: * @param sw a field widget
317: */
318: public FieldWidget makeWidget(SectionWidget sw) {
319: return new FieldWidget(sw, this );
320: }
321:
322: /**
323: * Returns the string that specifies this field's type in the report XML.
324: *
325: * @return a string representing this field's type; used in XML files
326: */
327: public abstract String typeString();
328:
329: /**
330: * Returns the string used to identify a field type when dragging.
331: * Usually returns {@link #typeString} plus a value or an id.
332: *
333: * @return the string used to identify the field when dragging
334: */
335: public abstract String dragString();
336:
337: /**
338: * Returns a string representing the field in the GUI during report design.
339: *
340: * @return a string useful for display in the design GUI
341: */
342: public String designLabel() {
343: return formulaString();
344: }
345:
346: /**
347: * Returns a string representing the field as it appears in a formula.
348: *
349: * @return a string useful in a formula
350: */
351: public abstract String formulaString();
352:
353: /**
354: * Returns <code>true</code> if this field contains a reference to the
355: * specified field. Most fields return <code>false</code>; only a {@link
356: * AggregateField} or {@link FormulaField} would return <code>true</code>.
357: *
358: * @param f a field
359: * @return <code>true</code> if this field contains a reference to the
360: * specified field
361: */
362: public boolean refersTo(Field f) {
363: return false;
364: }
365:
366: /**
367: * Returns <code>true</code> if this field contains a reference to the
368: * specified formula. Most fields return <code>false</code>; only a {@link
369: * AggregateField} or {@link FormulaField} would return <code>true</code>.
370: *
371: * @param f a formula
372: * @return <code>true</code> if this field contains a reference to the
373: * specified field
374: */
375: public boolean refersTo(Formula f) {
376: return false;
377: }
378:
379: /**
380: * Returns <code>true</code> if this field contains a reference to the
381: * specified user column. Most fields return <code>false</code>; only a {@link
382: * AggregateField}, {@link UserColumnField}, or {@link FormulaField} would
383: * return <code>true</code>.
384: *
385: * @param uc a user column
386: * @return <code>true</code> if this field contains a reference to the
387: * specified user column
388: */
389: public boolean refersTo(UserColumn uc) {
390: return false;
391: }
392:
393: /**
394: * Returns <code>true</code> if this field contains a reference to the
395: * specified parameter. Most fields return <code>false</code>; only a {@link
396: * AggregateField} or {@link FormulaField} would return <code>true</code>.
397: *
398: * @param p a parameter
399: * @return <code>true</code> if this field contains a reference to the
400: * specified field
401: */
402: public boolean refersTo(Parameter p) {
403: return false;
404: }
405:
406: /**
407: * Returns <code>true</code> if this field can be aggregated. This method
408: * returns <code>false</code> by default but is overridded by classes whose
409: * values may be aggregated.
410: *
411: * @return <code>true</code> if this field can be aggregated
412: */
413: public boolean canBeAggregated() {
414: return false;
415: }
416:
417: /**
418: * Returns this fields formatted value, ready for display in the report.
419: * If this field is invisible, or <code>getValue</code> returns
420: * <code>null</code> then this method will return <code>null</code>.
421: *
422: * @return the report display string; may be <code>null</code>
423: */
424: public String toString() {
425: if (!visible)
426: return null;
427: return cache.getFormattedString(getValue());
428: }
429:
430: /**
431: * Writes this field as an XML tag. Writes bounds, border, and format.
432: *
433: * @param out a writer that knows how to write XML
434: */
435: public void writeXML(XMLWriter out) {
436: out.startElement("field");
437: out.attr("id", id);
438: out.attr("type", typeString());
439: out.attr("value", value);
440: if (!visible)
441: out.attr("visible", visible);
442:
443: writeFieldGuts(out);
444:
445: out.endElement();
446: }
447:
448: /**
449: * Writes objects contained within this field (bounds, border, and format).
450: *
451: * @param out a writer that knows how to write XML
452: */
453: protected void writeFieldGuts(XMLWriter out) {
454: bounds.writeXML(out);
455: if (format != null)
456: format.writeXML(out);
457: if (border != null)
458: border.writeXML(out);
459: }
460:
461: }
|