OSDN Git Service

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