1 package net.argius.stew.ui.window;
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;
14 import java.util.List;
17 import javax.swing.table.*;
19 import net.argius.stew.*;
20 import net.argius.stew.io.*;
21 import net.argius.stew.ui.*;
24 * The OutputProcessor Implementation for GUI(Swing).
26 final class WindowOutputProcessor extends JFrame implements OutputProcessor, AnyActionListener {
28 private static final Logger log = Logger.getLogger(WindowOutputProcessor.class);
29 private static final ResourceManager res = ResourceManager.getInstance(WindowOutputProcessor.class);
31 private final AnyAction invoker;
33 private ResultSetTable resultSetTable;
34 private ConsoleTextArea textArea;
36 private Environment env;
37 private File currentDirectory;
38 private String postProcessMode;
40 WindowOutputProcessor(WindowLauncher launcher,
41 ResultSetTable resultSetTable,
42 ConsoleTextArea textArea) {
43 this.resultSetTable = resultSetTable;
44 this.textArea = textArea;
45 this.invoker = new AnyAction(this);
49 public void output(final Object o) {
51 if (o instanceof ResultSet) {
52 outputResult(new ResultSetReference((ResultSet)o, ""));
54 } else if (o instanceof ResultSetReference) {
55 outputResult((ResultSetReference)o);
58 } catch (SQLException ex) {
59 throw new RuntimeException("WindowOutputProcessor", ex);
62 if (o instanceof Prompt) {
63 message = o.toString();
65 message = String.format("%s%n", o);
67 AnyAction aa4text = new AnyAction(textArea);
68 AnyActionEvent ev = new AnyActionEvent(this,
69 ConsoleTextArea.ActionKey.outputMessage,
71 aa4text.doLater("anyActionPerformed", ev);
80 public void anyActionPerformed(AnyActionEvent ev) {
81 log.atEnter("anyActionPerformed", ev);
83 if (ev.isAnyOf(importFile)) {
84 importIntoCurrentTable();
85 } else if (ev.isAnyOf(exportFile)) {
87 } else if (ev.isAnyOf(showAbout)) {
90 log.warn("not expected: Event=%s", ev);
92 } catch (Exception ex) {
96 log.atExit("anyActionPerformed");
99 void setEnvironment(Environment env) {
101 if (currentDirectory == null) {
102 setCurrentDirectory(env.getCurrentDirectory());
109 * @throws SQLException
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",
128 if (rowCount >= limit) {
129 invoker.doLater("notifyOverLimit", limit);
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));
138 m.addRow((Vector<?>)v.clone());
139 if (env.getOutputProcessor() != opref) {
140 throw new SQLException("interrupted");
143 invoker.doLater("showResult", m);
144 ref.setRecordCount(m.getRowCount());
147 @SuppressWarnings("unused")
148 private void clearResultSetTable() {
149 resultSetTable.setVisible(false);
150 resultSetTable.getTableHeader().setVisible(false);
151 ((DefaultTableModel)resultSetTable.getModel()).setRowCount(0);
154 @SuppressWarnings("unused")
155 private void notifyOverLimit(int limit) {
156 output(res.get("w.exceeded-limit", limit));
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));
169 resultSetTable.anyActionPerformed(new AnyActionEvent(this, AnyActionKey.adjustColumnWidth));
170 resultSetTable.getTableHeader().setVisible(true);
171 resultSetTable.doLayout();
172 resultSetTable.setVisible(true);
175 String getPostProcessMode() {
176 return postProcessMode;
179 void setPostProcessMode(String postProcessMode) {
180 final String oldValue = this.postProcessMode;
181 this.postProcessMode = postProcessMode;
182 firePropertyChange("postProcessMode", oldValue, postProcessMode);
185 void doPostProcess() {
186 final JTextArea textArea = this.textArea;
187 final Runnable focusAction = new Runnable() {
191 textArea.requestFocusInWindow();
195 invokeLater(focusAction);
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:
205 case postProcessModeFocus:
206 invokeLater(new Runnable() {
214 case postProcessModeShake:
215 DaemonThreadFactory.execute(new Runnable() {
218 final Runnable shakeAction = new Runnable() {
223 setLocation(getX() + range * sign, getY());
226 invokeLater(new Runnable() {
229 textArea.requestFocusInWindow();
232 for (int i = 0, n = count >> 1 << 1; i < n; i++) {
233 invokeLater(shakeAction);
239 case postProcessModeBlink:
240 DaemonThreadFactory.execute(new Runnable() {
243 final byte[] alpha = {0};
244 final JPanel p = new JPanel() {
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());
252 invokeLater(new Runnable() {
255 textArea.requestFocusInWindow();
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);
266 invokeLater(new Runnable() {
277 log.warn("doPostProcess: postProcessMode=%s", postProcessMode);
282 * Imports data into the current table.
283 * @throws IOException
284 * @throws SQLException
286 void importIntoCurrentTable() throws IOException, SQLException {
287 if (env.getCurrentConnection() == null) {
288 showMessageDialog(this, res.get("w.not-connect"));
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());
300 showMessageDialog(this, res.get("w.import-target-not-available"));
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();
313 setCurrentDirectory(file);
314 Importer importer = Importer.getImporter(file);
317 Object[] row = importer.nextRow();
318 if (row.length == 0) {
321 m.addUnlinkedRow(row);
322 m.linkRow(m.getRowCount() - 1);
330 * Exports data in this table.
331 * @throws IOException
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();
343 setCurrentDirectory(file);
345 if (showConfirmDialog(this, res.get("i.confirm-overwrite", file), null, YES_NO_OPTION) != YES_OPTION) {
349 Exporter exporter = Exporter.getExporter(file);
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());
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());
366 showMessageDialog(this, res.get("i.exported"));
369 private void showVersionInfo() {
370 ImageIcon icon = new ImageIcon();
371 if (getIconImage() != null) {
372 icon.setImage(getIconImage());
374 final String about = res.get(".about", Bootstrap.getVersion());
375 showMessageDialog(this, about, null, PLAIN_MESSAGE, icon);
378 private void setCurrentDirectory(File file) {
380 if (file.isDirectory()) {
383 // when the file object is file, uses its parent's dir.
384 dir = file.getParentFile();
386 assert dir.isDirectory();
387 // I am optimistic about no-sync ...
388 currentDirectory = dir;
391 void showInformationMessageDialog(String message, String title) {
392 showInformationMessageDialog(this, message, title);
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);
401 void showErrorDialog(Throwable th) {
402 showErrorDialog(this, th);
405 static void showErrorDialog(final Component parent, final Throwable th) {
406 log.atEnter("showErrorDialog");
411 s1 = res.get("e.error-no-detail");
414 s1 = th.getMessage();
415 Writer buffer = new StringWriter();
416 PrintWriter out = new PrintWriter(buffer);
417 th.printStackTrace(out);
418 s2 = replaceEOL(buffer.toString());
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);
427 log.atExit("showErrorDialog");
430 private static String replaceEOL(String s) {
431 return s.replaceAll("\\\r\\\n?", "\n");
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));
444 * Bypass OutputProcessor for breaking command.
446 static final class Bypass implements OutputProcessor {
448 private OutputProcessor op;
449 private volatile boolean closed;
451 Bypass(OutputProcessor op) {
456 public void output(Object object) {
463 public void close() {