001: /*
002: #IFNDEF ALT_LICENSE
003: ThinWire(R) RIA Ajax Framework
004: Copyright (C) 2003-2007 Custom Credit Systems
005:
006: This library is free software; you can redistribute it and/or modify it under
007: the terms of the GNU Lesser General Public License as published by the Free
008: Software Foundation; either version 2.1 of the License, or (at your option) any
009: later version.
010:
011: This library is distributed in the hope that it will be useful, but WITHOUT ANY
012: WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
013: PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
014:
015: You should have received a copy of the GNU Lesser General Public License along
016: with this library; if not, write to the Free Software Foundation, Inc., 59
017: Temple Place, Suite 330, Boston, MA 02111-1307 USA
018:
019: Users who would rather have a commercial license, warranty or support should
020: contact the following company who invented, built and supports the technology:
021:
022: Custom Credit Systems, Richardson, TX 75081, USA.
023: email: info@thinwire.com ph: +1 (888) 644-6405
024: http://www.thinwire.com
025: #ENDIF
026: [ v1.2_RC2 ]
027: */
028: package thinwire.ui;
029:
030: import java.io.File;
031: import java.io.FileOutputStream;
032: import java.io.IOException;
033: import java.io.InputStream;
034: import java.util.ArrayList;
035: import java.util.Collections;
036: import java.util.List;
037: import java.util.logging.Logger;
038:
039: import thinwire.render.RenderStateEvent;
040: import thinwire.render.RenderStateListener;
041: import thinwire.render.web.WebApplication;
042: import thinwire.ui.event.ActionEvent;
043: import thinwire.ui.event.ActionListener;
044: import thinwire.ui.event.PropertyChangeEvent;
045: import thinwire.ui.event.PropertyChangeListener;
046: import thinwire.ui.style.Color;
047: import thinwire.ui.AlignTextComponent.AlignX;
048:
049: /**
050: * A <code>FileChooser</code> is a Component that enables a user to upload a
051: * file. It consists of a disabled TextField and a Button, that when clicked,
052: * causes the browser's file dialog to open. When the user chooses a file, the
053: * path of the file is displayed in the TextField.
054: * <p>
055: * <b>Example:</b> <br>
056: * <img src="doc-files/FileChooser-1.png"> <br>
057: *
058: * <pre>
059: * final Dialog dlg = new Dialog("FileChooser Test");
060: * dlg.setBounds(10, 10, 320, 100);
061: * final FileChooser fc = new FileChooser();
062: * fc.setBounds(10, 10, 300, 20);
063: * dlg.getChildren().add(fc);
064: *
065: * Button b = new Button("OK");
066: * b.setBounds(10, 40, 80, 25);
067: * b.addActionListener(Button.ACTION_CLICK, new ActionListener() {
068: * public void actionPerformed(ActionEvent ev) {
069: * try {
070: * FileInfo fi = fc.getFileInfo();
071: * File f = File.createTempFile("tw_upload"
072: * + System.currentTimeMillis(), ".txt");
073: * fi.saveToFile(f);
074: * Hyperlink h = new Hyperlink();
075: * h.setText("Click Here to See Your File");
076: * h.setLocation(f.getAbsolutePath());
077: * MessageBox mb = new MessageBox();
078: * mb.setTitle("File Chooser Test");
079: * mb.setComponent(h);
080: * mb.confirm();
081: * dlg.setVisible(false);
082: * } catch (Exception e) {
083: * throw new RuntimeException(e);
084: * }
085: * }
086: * });
087: * dlg.getChildren().add(b);
088: * dlg.setVisible(true);
089: * </pre>
090: *
091: * </p>
092: * <p>
093: * <b>Keyboard Navigation:</b><br>
094: * <table border="1">
095: * <tr>
096: * <td>KEY</td>
097: * <td>RESPONSE</td>
098: * <td>NOTE</td>
099: * </tr>
100: * </table>
101: * </p>
102: * @author Ted C. Howard
103: */
104: public class FileChooser extends Panel {
105:
106: /**
107: * <code>FileInfo</code> is a class containing the name of the file
108: * uploaded and its <code>InputStream</code>. It also contains
109: * convenience methods for saving the file.
110: *
111: * @author Joshua J. Gertzen and Ted C. Howard
112: */
113: public static class FileInfo {
114: String name;
115: String description;
116: InputStream is;
117:
118: public String getName() {
119: return name;
120: }
121:
122: public void setName(String name) {
123: this .name = name;
124: }
125:
126: public String getDescription() {
127: return description;
128: }
129:
130: /**
131: * For convenience, an arbitrary description can be assigned to each
132: * file uploaded.
133: *
134: * @param description
135: */
136: public void setDescription(String description) {
137: this .description = description;
138: }
139:
140: public InputStream getInputStream() {
141: return is;
142: }
143:
144: public void setInputStream(InputStream is) {
145: this .is = is;
146: }
147:
148: /**
149: * Writes the uploaded file to a new <code>File</code> at the
150: * specified location relative to the application base path.
151: *
152: * @param fileName
153: */
154: public void saveToFile(String fileName) {
155: saveToFile(Application.current().getRelativeFile(fileName));
156: }
157:
158: /**
159: * Writes the uploaded file to the specified <code>File</code>.
160: *
161: * @param file
162: */
163: public void saveToFile(File file) {
164: try {
165: FileOutputStream fos = new FileOutputStream(file);
166: byte[] block = new byte[256];
167: int length;
168: while ((length = is.read(block)) != -1)
169: fos.write(block, 0, length);
170: fos.close();
171: } catch (IOException e) {
172: throw new RuntimeException(e);
173: }
174: }
175: }
176:
177: private static final Logger log = Logger
178: .getLogger(FileChooser.class.getName());
179: private static final int BROWSE_BUTTON_WIDTH = 80;
180: private static final int BROWSE_BUTTON_HEIGHT = 20;
181: private static final int TEXT_FIELD_BUTTON_GAP = 5;
182: private static final int MAX_HEIGHT = 500;
183:
184: private WebApplication app;
185: private TextField fileName;
186: private Button browseButton;
187:
188: private PropertyChangeListener sizeListener = new PropertyChangeListener() {
189: public void propertyChange(PropertyChangeEvent ev) {
190: if (ev.getPropertyName().equals(Component.PROPERTY_WIDTH)) {
191: int newWidth = ((Container) ev.getSource())
192: .getInnerWidth();
193: int fileNameWidth = newWidth
194: - (BROWSE_BUTTON_WIDTH + TEXT_FIELD_BUTTON_GAP);
195: if (fileNameWidth < 0)
196: fileNameWidth = 0;
197: fileName.setWidth(fileNameWidth);
198: browseButton
199: .setX(fileNameWidth + TEXT_FIELD_BUTTON_GAP);
200: } else if (ev.getPropertyName().equals(
201: Component.PROPERTY_HEIGHT)) {
202: int newHeight = ((Container) ev.getSource())
203: .getInnerHeight();
204: if (newHeight > MAX_HEIGHT)
205: newHeight = MAX_HEIGHT;
206: fileName.setHeight(newHeight);
207: browseButton.setHeight(newHeight);
208: }
209: }
210: };
211:
212: public FileChooser() {
213: app = (WebApplication) Application.current();
214: List<Component> kids = super .getChildren();
215: getStyle().getBackground().setColor(Color.TRANSPARENT);
216:
217: fileName = new TextField();
218: fileName.setPosition(0, 0);
219: fileName.setHeight(BROWSE_BUTTON_HEIGHT);
220: fileName.setEnabled(false);
221: fileName.addPropertyChangeListener(TextField.PROPERTY_TEXT,
222: new PropertyChangeListener() {
223: public void propertyChange(PropertyChangeEvent ev) {
224: // Empty listener makes sure text validation is accurate
225: }
226: });
227: kids.add(fileName);
228:
229: browseButton = new Button("Browse");
230: browseButton.setSize(BROWSE_BUTTON_WIDTH, BROWSE_BUTTON_HEIGHT);
231: kids.add(browseButton);
232:
233: addPropertyChangeListener(new String[] { PROPERTY_WIDTH,
234: PROPERTY_HEIGHT }, sizeListener);
235: setSize(300, 20);
236:
237: app.addRenderStateListener(browseButton,
238: new RenderStateListener() {
239: public void renderStateChange(RenderStateEvent ev) {
240: app.clientSideMethodCall("tw_FileChooser",
241: "newInstance", app
242: .getComponentId(browseButton),
243: app.getComponentId(fileName));
244: }
245: });
246: }
247:
248: /**
249: * Initiates the upload of the file from the user's machine to the server.
250: * @return a <code>FileInfo</code> object for the file uploaded
251: */
252: public FileInfo getFileInfo() {
253: if (fileName.getText().length() == 0)
254: return null;
255: app.clientSideMethodCallWaitForReturn("tw_FileChooser",
256: "submit", app.getComponentId(fileName));
257: FileInfo fi = ((Application) app).getFileInfo();
258: return fi;
259: }
260:
261: @Override
262: public List<Component> getChildren() {
263: return Collections.unmodifiableList(super .getChildren());
264: }
265:
266: /**
267: * Displays a <code>Dialog</code> with a <code>FileChooser</code> along
268: * with OK and Cancel <code>Buttons</code>.
269: *
270: * @return the <code>FileInfo</code> for the file uploaded
271: */
272: public static FileInfo show() {
273: return show(false);
274: }
275:
276: /**
277: * Displays a <code>Dialog</code> with a <code>FileChooser</code> and
278: * optionally a description <code>TextField</code> along with OK and
279: * Cancel <code>Buttons</code>.
280: *
281: * @param showDescription
282: * displays a <code>TextField</code> for a description if true
283: * @return the <code>FileInfo</code> for the file uploaded
284: */
285: public static FileInfo show(boolean showDescription) {
286: List<FileInfo> l = show(showDescription, false);
287: return l.size() > 0 ? l.get(0) : null;
288: }
289:
290: /**
291: * Displays a <code>Dialog</code> with a <code>FileChooser</code> and
292: * optionally a description <code>TextField</code> along with OK and
293: * Cancel <code>Buttons</code>. Optional <code>Buttons</code> are
294: * availiable for adding and removing additional <code>FileChooser</code>
295: * components.
296: *
297: * @param showDescription
298: * displays a <code>TextField</code> for a description if true
299: * @param multiFile
300: * displays Add and Remove <code>Buttons</code> for uploading multiple files
301: * @return a <code>List</code> of <code>FileInfo</code> objects
302: */
303: public static List<FileInfo> show(final boolean showDescription,
304: boolean multiFile) {
305: final Frame f = Application.current().getFrame();
306: final Dialog dlg = new Dialog(
307: multiFile ? "Upload Multiple Files" : "Upload File");
308: dlg.setWaitForWindow(true);
309: final List<Component> kids = dlg.getChildren();
310: final List<Component[]> files = new ArrayList<Component[]>();
311: final List<FileInfo> fileInfoList = new ArrayList<FileInfo>();
312:
313: int width = 600;
314: int height = showDescription ? 121 : 98;
315: final int incHeight = showDescription ? 50 : 25;
316:
317: dlg.setBounds((f.getWidth() - width) / 2,
318: (f.getHeight() - height) / 2, width, height);
319: files.add(addRow(dlg, 0, showDescription));
320:
321: final Button okBtn = new Button("OK");
322: okBtn.setBounds(dlg.getInnerWidth() - 170,
323: dlg.getInnerHeight() - 27, 80, 22);
324: okBtn.addActionListener(Button.ACTION_CLICK,
325: new ActionListener() {
326: public void actionPerformed(ActionEvent ev) {
327: for (Component[] c : files) {
328: FileChooser fc = (FileChooser) c[0];
329: FileInfo fi = fc.getFileInfo();
330: if (fi != null) {
331: if (c.length == 2)
332: fi
333: .setDescription(((TextField) c[1])
334: .getText());
335: fileInfoList.add(fi);
336: }
337: }
338: dlg.setVisible(false);
339: }
340: });
341: kids.add(okBtn);
342:
343: final Button cancelBtn = new Button("Cancel");
344: cancelBtn.setBounds(dlg.getInnerWidth() - 85, dlg
345: .getInnerHeight() - 27, 80, 22);
346: cancelBtn.addActionListener(Button.ACTION_CLICK,
347: new ActionListener() {
348: public void actionPerformed(ActionEvent ev) {
349: dlg.setVisible(false);
350: }
351: });
352: kids.add(cancelBtn);
353:
354: if (multiFile) {
355: Button addBtn = new Button("Add");
356: final Button removeBtn = new Button("Remove");
357:
358: addBtn.setBounds(5, dlg.getInnerHeight() - 27, 80, 22);
359: addBtn.addActionListener(Button.ACTION_CLICK,
360: new ActionListener() {
361: public void actionPerformed(ActionEvent ev) {
362: dlg.setHeight(dlg.getHeight() + incHeight);
363: dlg
364: .setY((f.getHeight() - dlg
365: .getHeight()) / 2);
366: files.add(addRow(dlg, files.size(),
367: showDescription));
368: if (files.size() > 1)
369: removeBtn.setEnabled(true);
370: }
371: });
372: kids.add(addBtn);
373:
374: removeBtn.setBounds(90, dlg.getInnerHeight() - 27, 80, 22);
375: removeBtn.setEnabled(false);
376: removeBtn.addActionListener(Button.ACTION_CLICK,
377: new ActionListener() {
378: public void actionPerformed(ActionEvent ev) {
379: Component[] lastFile = files.remove(files
380: .size() - 1);
381: for (Component c : lastFile) {
382: kids.remove(c.getLabel());
383: kids.remove(c);
384: }
385: dlg.setHeight(dlg.getHeight() - incHeight);
386: dlg
387: .setY((f.getHeight() - dlg
388: .getHeight()) / 2);
389: if (files.size() <= 1)
390: ((Button) ev.getSource())
391: .setEnabled(false);
392: }
393: });
394: kids.add(removeBtn);
395: }
396:
397: dlg.addPropertyChangeListener(Dialog.PROPERTY_HEIGHT,
398: new PropertyChangeListener() {
399: public void propertyChange(PropertyChangeEvent ev) {
400: int newY = ((Dialog) ev.getSource())
401: .getInnerHeight() - 27;
402: for (Component c : ((Dialog) ev.getSource())
403: .getChildren()) {
404: if (c instanceof Button)
405: c.setY(newY);
406: }
407: }
408: });
409:
410: dlg.setVisible(true);
411: return fileInfoList;
412: }
413:
414: private static Component[] addRow(Dialog dlg, int rowCount,
415: boolean showDescription) {
416: List<Component> kids = dlg.getChildren();
417: int y1 = showDescription ? 10 + (rowCount * 50)
418: : 10 + (rowCount * 20);
419: FileChooser fc = new FileChooser();
420: fc.setBounds(85, y1, dlg.getInnerWidth() - 90, 20);
421: kids.add(fc);
422: Label fcl = new Label("File Name:");
423: fcl.setBounds(10, y1, 70, 20);
424: fcl.setAlignX(AlignX.RIGHT);
425: fcl.setLabelFor(fc);
426: kids.add(fcl);
427: if (showDescription) {
428: int y2 = y1 + 25;
429: TextField description = new TextField();
430: description.setBounds(85, y2, dlg.getInnerWidth() - 90, 20);
431: kids.add(description);
432:
433: Label lblDescription = new Label("Description:");
434: lblDescription.setBounds(10, y2, 70, 20);
435: lblDescription.setAlignX(AlignX.RIGHT);
436: lblDescription.setLabelFor(description);
437: kids.add(lblDescription);
438: return new Component[] { fc, description };
439: } else {
440: return new Component[] { fc };
441: }
442: }
443: }
|