1 package net.argius.stew.ui.window;
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;
9 import java.awt.event.*;
13 import java.util.List;
15 import javax.swing.table.*;
16 import net.argius.stew.*;
17 import net.argius.stew.io.*;
18 import net.argius.stew.ui.*;
21 * The OutputProcessor Implementation for GUI(Swing).
23 final class WindowOutputProcessor extends JFrame implements OutputProcessor, AnyActionListener {
25 private static final Logger log = Logger.getLogger(WindowOutputProcessor.class);
26 private static final ResourceManager res = ResourceManager.getInstance(WindowOutputProcessor.class);
28 private final AnyAction invoker;
29 private final WindowLauncher launcher;
30 private final ResultSetTable resultSetTable;
31 private final ConsoleTextArea textArea;
33 private Environment env;
34 private File currentDirectory;
35 private String postProcessMode;
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);
47 public void output(final Object o) {
49 if (o instanceof ResultSet) {
50 outputResult(new ResultSetReference((ResultSet)o, ""));
52 } else if (o instanceof ResultSetReference) {
53 outputResult((ResultSetReference)o);
56 } catch (SQLException ex) {
57 throw new RuntimeException("WindowOutputProcessor", ex);
60 if (o instanceof Prompt) {
61 message = o.toString();
63 message = String.format("%s%n", o);
65 AnyAction aa4text = new AnyAction(textArea);
66 AnyActionEvent ev = new AnyActionEvent(this,
67 ConsoleTextArea.ActionKey.outputMessage,
69 aa4text.doLater("anyActionPerformed", ev);
78 protected void processWindowEvent(WindowEvent e) {
79 if (e.getID() == WindowEvent.WINDOW_CLOSING) {
80 launcher.anyActionPerformed(new AnyActionEvent(this, AnyActionKey.closeWindow));
85 public void anyActionPerformed(AnyActionEvent ev) {
86 log.atEnter("anyActionPerformed", ev);
88 if (ev.isAnyOf(importFile)) {
89 importIntoCurrentTable();
90 } else if (ev.isAnyOf(exportFile)) {
92 } else if (ev.isAnyOf(showAbout)) {
95 log.warn("not expected: Event=%s", ev);
97 } catch (Exception ex) {
101 log.atExit("anyActionPerformed");
104 void setEnvironment(Environment env) {
106 if (currentDirectory == null) {
107 setCurrentDirectory(env.getCurrentDirectory());
114 * @throws SQLException
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",
133 if (rowCount >= limit) {
134 invoker.doLater("notifyOverLimit", limit);
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));
143 m.addRow((Vector<?>)v.clone());
144 if (env.getOutputProcessor() != opref) {
145 throw new SQLException("interrupted");
148 invoker.doLater("showResult", m);
149 ref.setRecordCount(m.getRowCount());
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);
158 resultSetTable.setVisible(false);
159 resultSetTable.getTableHeader().setVisible(false);
160 ((DefaultTableModel)resultSetTable.getModel()).setRowCount(0);
161 resultSetTable.resetSortState();
164 @SuppressWarnings("unused")
165 private void notifyOverLimit(int limit) {
166 output(res.get("w.exceeded-limit", limit));
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));
179 resultSetTable.anyActionPerformed(new AnyActionEvent(this, AnyActionKey.adjustColumnWidth));
180 resultSetTable.getTableHeader().setVisible(true);
181 resultSetTable.doLayout();
182 resultSetTable.setVisible(true);
185 String getPostProcessMode() {
186 return postProcessMode;
189 void setPostProcessMode(String postProcessMode) {
190 final String oldValue = this.postProcessMode;
191 this.postProcessMode = postProcessMode;
192 firePropertyChange("postProcessMode", oldValue, postProcessMode);
195 void doPostProcess() {
196 final AnyAction aa = new AnyAction(this);
198 aa.doLater("focusWindow", true);
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:
208 case postProcessModeFocus:
209 aa.doLater("focusWindow", false);
211 case postProcessModeShake:
212 aa.doParallel("shakeWindow", count, range, interval);
214 case postProcessModeBlink:
215 aa.doParallel("blinkWindow", count, range, interval);
218 log.warn("doPostProcess: postProcessMode=%s", postProcessMode);
222 void focusWindow(boolean moveToFront) {
227 textArea.requestFocusInWindow();
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);
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);
249 aa.doLater("removeComponent", p);
252 void requestFocusToTextAreaInWindow() {
253 textArea.requestFocusInWindow();
256 final class PostProcessAction extends JPanel {
259 Frame frame = getRootFrame();
260 PostProcessAction(byte... alpha) {
264 requestFocusToTextAreaInWindow();
266 void shakeWindow(int range) {
268 setLocation(getX() + range * sign, getY() + 0);
270 void showComponent(JComponent c) {
271 requestFocusToTextAreaInWindow();
276 void removeComponent(JComponent c) {
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());
289 * Imports data into the current table.
290 * @throws IOException
291 * @throws SQLException
293 void importIntoCurrentTable() throws IOException, SQLException {
294 if (env.getCurrentConnection() == null) {
295 showMessageDialog(this, res.get("w.not-connect"));
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());
307 showMessageDialog(this, res.get("w.import-target-not-available"));
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();
320 setCurrentDirectory(file);
321 Importer importer = Importer.getImporter(file);
324 Object[] row = importer.nextRow();
325 if (row.length == 0) {
328 m.addUnlinkedRow(row);
329 m.linkRow(m.getRowCount() - 1);
337 * Exports data in this table.
338 * @throws IOException
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();
350 setCurrentDirectory(file);
352 if (showConfirmDialog(this, res.get("i.confirm-overwrite", file), null, YES_NO_OPTION) != YES_OPTION) {
356 Exporter exporter = Exporter.getExporter(file);
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());
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());
373 showMessageDialog(this, res.get("i.exported"));
376 private void showVersionInfo() {
377 ImageIcon icon = new ImageIcon();
378 if (getIconImage() != null) {
379 icon.setImage(getIconImage());
381 final String about = res.get(".about", Bootstrap.getVersion());
382 showMessageDialog(this, about, null, PLAIN_MESSAGE, icon);
385 private void setCurrentDirectory(File file) {
387 if (file.isDirectory()) {
390 // when the file object is file, uses its parent's dir.
391 dir = file.getParentFile();
393 assert dir.isDirectory();
394 // I am optimistic about no-sync ...
395 currentDirectory = dir;
398 Object showInputDialog(String message, String title, Object[] values, Object initial) {
399 return showInputDialog(this, message, title, values, initial);
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);
414 if (size.height > parent.getHeight()) {
415 size.height = (int)(parent.getHeight() * 0.95);
417 d.setPreferredSize(size);
419 d.setLocationRelativeTo(parent);
421 p.selectInitialValue();
424 Object value = p.getInputValue();
425 return (value == UNINITIALIZED_VALUE) ? null : value;
428 void showInformationMessageDialog(String message, String title) {
429 showInformationMessageDialog(this, message, title);
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);
438 void showErrorDialog(Throwable th) {
439 showErrorDialog(this, th);
442 static void showErrorDialog(final Component parent, final Throwable th) {
443 // this method can call from non AWT thread
444 log.atEnter("showErrorDialog");
449 s1 = res.get("e.error-no-detail");
452 s1 = th.getMessage();
453 Writer buffer = new StringWriter();
454 PrintWriter out = new PrintWriter(buffer);
455 th.printStackTrace(out);
456 s2 = replaceEOL(buffer.toString());
458 final String title = res.get("e.error");
459 class ErrorDialogTask implements Runnable {
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);
471 ErrorDialogTask task = new ErrorDialogTask();
472 if (EventQueue.isDispatchThread()) {
475 EventQueue.invokeLater(task);
477 log.atExit("showErrorDialog");
480 private static String replaceEOL(String s) {
481 return s.replaceAll("\\\r\\\n?", "\n");
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));
494 * Bypass OutputProcessor for breaking command.
496 static final class Bypass implements OutputProcessor {
498 private OutputProcessor op;
499 private volatile boolean closed;
501 Bypass(OutputProcessor op) {
506 public void output(Object object) {
513 public void close() {