OSDN Git Service

make it possible to call WindowOutputProcessor.showErrorDialog from non
[stew/Stew4.git] / src / net / argius / stew / ui / window / WindowOutputProcessor.java
1 package net.argius.stew.ui.window;
2
3 import static javax.swing.JOptionPane.*;
4 import static net.argius.stew.Bootstrap.getPropertyAsInt;
5 import static net.argius.stew.ui.window.AnyActionKey.*;
6 import static net.argius.stew.ui.window.Utilities.getImageIcon;
7 import static net.argius.stew.ui.window.Utilities.sleep;
8 import java.awt.*;
9 import java.awt.event.*;
10 import java.io.*;
11 import java.sql.*;
12 import java.util.*;
13 import java.util.List;
14 import javax.swing.*;
15 import javax.swing.table.*;
16 import net.argius.stew.*;
17 import net.argius.stew.io.*;
18 import net.argius.stew.ui.*;
19
20 /**
21  * The OutputProcessor Implementation for GUI(Swing).
22  */
23 final class WindowOutputProcessor extends JFrame implements OutputProcessor, AnyActionListener {
24
25     private static final Logger log = Logger.getLogger(WindowOutputProcessor.class);
26     private static final ResourceManager res = ResourceManager.getInstance(WindowOutputProcessor.class);
27
28     private final AnyAction invoker;
29     private final WindowLauncher launcher;
30     private final ResultSetTable resultSetTable;
31     private final ConsoleTextArea textArea;
32
33     private Environment env;
34     private File currentDirectory;
35     private String postProcessMode;
36
37     WindowOutputProcessor(WindowLauncher launcher,
38                           ResultSetTable resultSetTable,
39                           ConsoleTextArea textArea) {
40         this.launcher = launcher;
41         this.resultSetTable = resultSetTable;
42         this.textArea = textArea;
43         this.invoker = new AnyAction(this);
44     }
45
46     @Override
47     public void output(final Object o) {
48         try {
49             if (o instanceof ResultSet) {
50                 outputResult(new ResultSetReference((ResultSet)o, ""));
51                 return;
52             } else if (o instanceof ResultSetReference) {
53                 outputResult((ResultSetReference)o);
54                 return;
55             }
56         } catch (SQLException ex) {
57             throw new RuntimeException("WindowOutputProcessor", ex);
58         }
59         final String message;
60         if (o instanceof Prompt) {
61             message = o.toString();
62         } else {
63             message = String.format("%s%n", o);
64         }
65         AnyAction aa4text = new AnyAction(textArea);
66         AnyActionEvent ev = new AnyActionEvent(this,
67                                                ConsoleTextArea.ActionKey.outputMessage,
68                                                replaceEOL(message));
69         aa4text.doLater("anyActionPerformed", ev);
70     }
71
72     @Override
73     public void close() {
74         dispose();
75     }
76
77     @Override
78     protected void processWindowEvent(WindowEvent e) {
79         if (e.getID() == WindowEvent.WINDOW_CLOSING) {
80             launcher.anyActionPerformed(new AnyActionEvent(this, AnyActionKey.closeWindow));
81         }
82     }
83
84     @Override
85     public void anyActionPerformed(AnyActionEvent ev) {
86         log.atEnter("anyActionPerformed", ev);
87         try {
88             if (ev.isAnyOf(importFile)) {
89                 importIntoCurrentTable();
90             } else if (ev.isAnyOf(exportFile)) {
91                 exportTableContent();
92             } else if (ev.isAnyOf(showAbout)) {
93                 showVersionInfo();
94             } else {
95                 log.warn("not expected: Event=%s", ev);
96             }
97         } catch (Exception ex) {
98             log.error(ex);
99             showErrorDialog(ex);
100         }
101         log.atExit("anyActionPerformed");
102     }
103
104     void setEnvironment(Environment env) {
105         this.env = env;
106         if (currentDirectory == null) {
107             setCurrentDirectory(env.getCurrentDirectory());
108         }
109     }
110
111     /**
112      * Outputs a result.
113      * @param ref
114      * @throws SQLException
115      */
116     void outputResult(ResultSetReference ref) throws SQLException {
117         // NOTICE: This method will be called by non AWT thread.
118         // To access GUI, use "Later".
119         final OutputProcessor opref = env.getOutputProcessor();
120         invoker.doLater("clearResultSetTable");
121         ResultSet rs = ref.getResultSet();
122         ColumnOrder order = ref.getOrder();
123         final boolean needsOrderChange = order.size() > 0;
124         ResultSetMetaData meta = rs.getMetaData();
125         final int columnCount = (needsOrderChange) ? order.size() : meta.getColumnCount();
126         final ResultSetTableModel m = new ResultSetTableModel(ref);
127         Vector<Object> v = new Vector<Object>(columnCount);
128         ValueTransporter transfer = ValueTransporter.getInstance("");
129         final int limit = Bootstrap.getPropertyAsInt("net.argius.stew.rowcount.limit",
130                                                      Integer.MAX_VALUE);
131         int rowCount = 0;
132         while (rs.next()) {
133             if (rowCount >= limit) {
134                 invoker.doLater("notifyOverLimit", limit);
135                 break;
136             }
137             ++rowCount;
138             v.clear();
139             for (int i = 0; i < columnCount; i++) {
140                 final int index = needsOrderChange ? order.getOrder(i) : i + 1;
141                 v.add(transfer.getObject(rs, index));
142             }
143             m.addRow((Vector<?>)v.clone());
144             if (env.getOutputProcessor() != opref) {
145                 throw new SQLException("interrupted");
146             }
147         }
148         invoker.doLater("showResult", m);
149         ref.setRecordCount(m.getRowCount());
150     }
151
152     @SuppressWarnings("unused")
153     private void clearResultSetTable() {
154         Container p = resultSetTable.getParent();
155         if (p != null && p.getParent() instanceof JScrollPane) {
156             ((JScrollPane)p.getParent()).setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER, null);
157         }
158         resultSetTable.setVisible(false);
159         resultSetTable.getTableHeader().setVisible(false);
160         ((DefaultTableModel)resultSetTable.getModel()).setRowCount(0);
161         resultSetTable.resetSortState();
162     }
163
164     @SuppressWarnings("unused")
165     private void notifyOverLimit(int limit) {
166         output(res.get("w.exceeded-limit", limit));
167     }
168
169     @SuppressWarnings("unused")
170     private void showResult(ResultSetTableModel m) {
171         resultSetTable.setModel(m);
172         Container p = resultSetTable.getParent();
173         if (p != null && p.getParent() instanceof JScrollPane) {
174             JScrollPane scrollPane = (JScrollPane)p.getParent();
175             ImageIcon icon = getImageIcon(String.format("linkable-%s.png", m.isLinkable()));
176             scrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,
177                                  new JLabel(icon, SwingConstants.CENTER));
178         }
179         resultSetTable.anyActionPerformed(new AnyActionEvent(this, AnyActionKey.adjustColumnWidth));
180         resultSetTable.getTableHeader().setVisible(true);
181         resultSetTable.doLayout();
182         resultSetTable.setVisible(true);
183     }
184
185     String getPostProcessMode() {
186         return postProcessMode;
187     }
188
189     void setPostProcessMode(String postProcessMode) {
190         final String oldValue = this.postProcessMode;
191         this.postProcessMode = postProcessMode;
192         firePropertyChange("postProcessMode", oldValue, postProcessMode);
193     }
194
195     void doPostProcess() {
196         final AnyAction aa = new AnyAction(this);
197         if (isActive()) {
198             aa.doLater("focusWindow", true);
199             return;
200         }
201         final String prefix = getClass().getName() + ".postprocess.";
202         final int count = getPropertyAsInt(prefix + "count", 32);
203         final int range = getPropertyAsInt(prefix + "range", 2);
204         final long interval = getPropertyAsInt(prefix + "interval", 50);
205         switch (AnyActionKey.of(postProcessMode)) {
206             case postProcessModeNone:
207                 break;
208             case postProcessModeFocus:
209                 aa.doLater("focusWindow", false);
210                 break;
211             case postProcessModeShake:
212                 aa.doParallel("shakeWindow", count, range, interval);
213                 break;
214             case postProcessModeBlink:
215                 aa.doParallel("blinkWindow", count, range, interval);
216                 break;
217             default:
218                 log.warn("doPostProcess: postProcessMode=%s", postProcessMode);
219         }
220     }
221
222     void focusWindow(boolean moveToFront) {
223         if (moveToFront) {
224             toFront();
225         }
226         requestFocus();
227         textArea.requestFocusInWindow();
228     }
229
230     void shakeWindow(int count, int range, long interval) {
231         AnyAction aa = new AnyAction(new PostProcessAction());
232         aa.doLater("focusWindow");
233         for (int i = 0, n = count >> 1 << 1; i < n; i++) {
234             aa.doLater("shakeWindow", range);
235             sleep(interval);
236         }
237     }
238
239     void blinkWindow(final int count, final int range, final long interval) {
240         final byte[] alpha = {0};
241         final JPanel p = new PostProcessAction(alpha);
242         AnyAction aa = new AnyAction(new PostProcessAction());
243         aa.doLater("showComponent", p);
244         for (int i = 0, n = (count / 45 + 1) * 45; i < n; i++) {
245             alpha[0] = (byte)((Math.sin(i * 0.25f) + 1) * 32 * range);
246             p.repaint();
247             sleep(interval);
248         }
249         aa.doLater("removeComponent", p);
250     }
251
252     void requestFocusToTextAreaInWindow() {
253         textArea.requestFocusInWindow();
254     }
255
256     final class PostProcessAction extends JPanel {
257         int sign = -1;
258         byte[] alpha;
259         Frame frame = getRootFrame();
260         PostProcessAction(byte... alpha) {
261             this.alpha = alpha;
262         }
263         void focusWindow() {
264             requestFocusToTextAreaInWindow();
265         }
266         void shakeWindow(int range) {
267             sign *= -1;
268             setLocation(getX() + range * sign, getY() + 0);
269         }
270         void showComponent(JComponent c) {
271             requestFocusToTextAreaInWindow();
272             setGlassPane(c);
273             c.setOpaque(false);
274             c.setVisible(true);
275         }
276         void removeComponent(JComponent c) {
277             c.setVisible(false);
278             remove(c);
279         }
280         @Override
281         public void paintComponent(Graphics g) {
282             super.paintComponents(g);
283             g.setColor(new Color(0, 0xE6, 0x2E, alpha[0] & 0xFF));
284             g.fillRect(0, 0, getWidth(), getHeight());
285         }
286     }
287
288     /**
289      * Imports data into the current table.
290      * @throws IOException
291      * @throws SQLException
292      */
293     void importIntoCurrentTable() throws IOException, SQLException {
294         if (env.getCurrentConnection() == null) {
295             showMessageDialog(this, res.get("w.not-connect"));
296             return;
297         }
298         TableModel tm = resultSetTable.getModel();
299         final boolean importable;
300         if (tm instanceof ResultSetTableModel) {
301             ResultSetTableModel m = (ResultSetTableModel)tm;
302             importable = m.isLinkable() && m.isSameConnection(env.getCurrentConnection());
303         } else {
304             importable = false;
305         }
306         if (!importable) {
307             showMessageDialog(this, res.get("w.import-target-not-available"));
308             return;
309         }
310         ResultSetTableModel m = (ResultSetTableModel)tm;
311         assert currentDirectory != null;
312         JFileChooser fileChooser = new JFileChooser(currentDirectory);
313         fileChooser.setDialogTitle(res.get("Action.import"));
314         fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
315         fileChooser.showOpenDialog(this);
316         final File file = fileChooser.getSelectedFile();
317         if (file == null) {
318             return;
319         }
320         setCurrentDirectory(file);
321         Importer importer = Importer.getImporter(file);
322         try {
323             while (true) {
324                 Object[] row = importer.nextRow();
325                 if (row.length == 0) {
326                     break;
327                 }
328                 m.addUnlinkedRow(row);
329                 m.linkRow(m.getRowCount() - 1);
330             }
331         } finally {
332             importer.close();
333         }
334     }
335
336     /**
337      * Exports data in this table.
338      * @throws IOException
339      */
340     void exportTableContent() throws IOException {
341         assert currentDirectory != null;
342         JFileChooser fileChooser = new JFileChooser(currentDirectory);
343         fileChooser.setDialogTitle(res.get("Action.export"));
344         fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
345         fileChooser.showSaveDialog(this);
346         final File file = fileChooser.getSelectedFile();
347         if (file == null) {
348             return;
349         }
350         setCurrentDirectory(file);
351         if (file.exists()) {
352             if (showConfirmDialog(this, res.get("i.confirm-overwrite", file), null, YES_NO_OPTION) != YES_OPTION) {
353                 return;
354             }
355         }
356         Exporter exporter = Exporter.getExporter(file);
357         try {
358             TableColumnModel columnModel = resultSetTable.getTableHeader().getColumnModel();
359             List<Object> headerValues = new ArrayList<Object>();
360             for (TableColumn column : Collections.list(columnModel.getColumns())) {
361                 headerValues.add(column.getHeaderValue());
362             }
363             exporter.addHeader(headerValues.toArray());
364             DefaultTableModel m = (DefaultTableModel)resultSetTable.getModel();
365             @SuppressWarnings("unchecked")
366             Vector<Vector<Object>> rows = m.getDataVector();
367             for (Vector<Object> row : rows) {
368                 exporter.addRow(row.toArray());
369             }
370         } finally {
371             exporter.close();
372         }
373         showMessageDialog(this, res.get("i.exported"));
374     }
375
376     private void showVersionInfo() {
377         ImageIcon icon = new ImageIcon();
378         if (getIconImage() != null) {
379             icon.setImage(getIconImage());
380         }
381         final String about = res.get(".about", Bootstrap.getVersion());
382         showMessageDialog(this, about, null, PLAIN_MESSAGE, icon);
383     }
384
385     private void setCurrentDirectory(File file) {
386         final File dir;
387         if (file.isDirectory()) {
388             dir = file;
389         } else {
390             // when the file object is file, uses its parent's dir.
391             dir = file.getParentFile();
392         }
393         assert dir.isDirectory();
394         // I am optimistic about no-sync ...
395         currentDirectory = dir;
396     }
397
398     Object showInputDialog(String message, String title, Object[] values, Object initial) {
399         return showInputDialog(this, message, title, values, initial);
400     }
401
402     static Object showInputDialog(Component parent, String message, String title, Object[] values, Object initial) {
403         JOptionPane p = new JOptionPane(message, PLAIN_MESSAGE, OK_CANCEL_OPTION);
404         p.setWantsInput(true);
405         p.setSelectionValues(values);
406         p.setInitialSelectionValue(initial);
407         p.setComponentOrientation(parent.getComponentOrientation());
408         JDialog d = p.createDialog(parent, title);
409         Dimension size = d.getSize();
410         if (size.width > parent.getWidth() || size.height > parent.getHeight()) {
411             if (size.width > parent.getWidth()) {
412                 size.width = (int)(parent.getWidth() * 0.95);
413             }
414             if (size.height > parent.getHeight()) {
415                 size.height = (int)(parent.getHeight() * 0.95);
416             }
417             d.setPreferredSize(size);
418             d.setSize(size);
419             d.setLocationRelativeTo(parent);
420         }
421         p.selectInitialValue();
422         d.setVisible(true);
423         d.dispose();
424         Object value = p.getInputValue();
425         return (value == UNINITIALIZED_VALUE) ? null : value;
426     }
427
428     void showInformationMessageDialog(String message, String title) {
429         showInformationMessageDialog(this, message, title);
430     }
431
432     static void showInformationMessageDialog(final Component parent, String message, String title) {
433         JTextArea textArea = new JTextArea(message, 6, 60);
434         setupReadOnlyTextArea(textArea);
435         showMessageDialog(parent, new JScrollPane(textArea), title, INFORMATION_MESSAGE);
436     }
437
438     void showErrorDialog(Throwable th) {
439         showErrorDialog(this, th);
440     }
441
442     static void showErrorDialog(final Component parent, final Throwable th) {
443         // this method can call from non AWT thread
444         log.atEnter("showErrorDialog");
445         log.warn(th, "");
446         final String s1;
447         final String s2;
448         if (th == null) {
449             s1 = res.get("e.error-no-detail");
450             s2 = "";
451         } else {
452             s1 = th.getMessage();
453             Writer buffer = new StringWriter();
454             PrintWriter out = new PrintWriter(buffer);
455             th.printStackTrace(out);
456             s2 = replaceEOL(buffer.toString());
457         }
458         final String title = res.get("e.error");
459         class ErrorDialogTask implements Runnable {
460             @Override
461             public void run() {
462                 JPanel p = new JPanel(new BorderLayout());
463                 p.add(new JScrollPane(setupReadOnlyTextArea(new JTextArea(s1, 2, 60))), BorderLayout.NORTH);
464                 p.add(new JScrollPane(setupReadOnlyTextArea(new JTextArea(s2, 6, 60))), BorderLayout.CENTER);
465                 JDialog d = (new JOptionPane(p, ERROR_MESSAGE)).createDialog(parent, title);
466                 d.setResizable(true);
467                 d.setVisible(true);
468                 d.dispose();
469             }
470         }
471         ErrorDialogTask task = new ErrorDialogTask();
472         if (EventQueue.isDispatchThread()) {
473             task.run();
474         } else {
475             EventQueue.invokeLater(task);
476         }
477         log.atExit("showErrorDialog");
478     }
479
480     private static String replaceEOL(String s) {
481         return s.replaceAll("\\\r\\\n?", "\n");
482     }
483
484     static JTextArea setupReadOnlyTextArea(JTextArea textArea) {
485         textArea.setEditable(false);
486         textArea.setWrapStyleWord(false);
487         textArea.setLineWrap(false);
488         textArea.setOpaque(false);
489         textArea.setMargin(new Insets(4, 4, 4, 4));
490         return textArea;
491     }
492
493     /**
494      * Bypass OutputProcessor for breaking command.
495      */
496     static final class Bypass implements OutputProcessor {
497
498         private OutputProcessor op;
499         private volatile boolean closed;
500
501         Bypass(OutputProcessor op) {
502             this.op = op;
503         }
504
505         @Override
506         public void output(Object object) {
507             if (!closed) {
508                 op.output(object);
509             }
510         }
511
512         @Override
513         public void close() {
514             closed = true;
515         }
516
517     }
518
519 }