OSDN Git Service

refactoring: factory functions in Menu and ContextMenu
[stew/Stew4.git] / src / net / argius / stew / ui / window / WindowLauncher.java
1 package net.argius.stew.ui.window;
2
3 import static java.awt.event.ActionEvent.ACTION_PERFORMED;
4 import static javax.swing.JOptionPane.*;
5 import static javax.swing.JSplitPane.VERTICAL_SPLIT;
6 import static javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER;
7 import static javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
8 import static net.argius.stew.ui.window.AnyActionKey.*;
9
10 import java.awt.*;
11 import java.awt.event.*;
12 import java.beans.*;
13 import java.io.*;
14 import java.lang.Thread.UncaughtExceptionHandler;
15 import java.sql.*;
16 import java.util.*;
17 import java.util.Map.Entry;
18 import java.util.List;
19 import java.util.Timer;
20 import java.util.concurrent.*;
21
22 import javax.swing.*;
23 import javax.swing.event.*;
24 import javax.swing.table.*;
25 import javax.swing.text.*;
26
27 import net.argius.stew.*;
28 import net.argius.stew.ui.*;
29
30 /**
31  * The Launcher implementation for GUI(Swing).
32  */
33 public final class WindowLauncher implements
34                                  Launcher,
35                                  AnyActionListener,
36                                  Runnable,
37                                  UncaughtExceptionHandler {
38
39     private static final Logger log = Logger.getLogger(WindowLauncher.class);
40     private static final ResourceManager res = ResourceManager.getInstance(WindowLauncher.class);
41     private static final List<WindowLauncher> instances = Collections.synchronizedList(new ArrayList<WindowLauncher>());
42
43     private final WindowOutputProcessor op;
44     private final Menu menu;
45     private final JPanel panel1;
46     private final JPanel panel2;
47     private final JSplitPane split1;
48     private final JSplitPane split2;
49     private final ResultSetTable resultSetTable;
50     private final ConsoleTextArea textArea;
51     private final DatabaseInfoTree infoTree;
52     private final TextSearchPanel textSearchPanel;
53     private final JLabel statusBar;
54     private final List<String> historyList;
55     private final ExecutorService executorService;
56
57     private Environment env;
58     private Map<JComponent, TextSearch> textSearchMap;
59     private int historyIndex;
60     private JComponent focused;
61
62     WindowLauncher() {
63         // [Instances]
64         instances.add(this);
65         final JSplitPane split1 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
66         final DatabaseInfoTree infoTree = new DatabaseInfoTree(this);
67         final ResultSetTable resultSetTable = new ResultSetTable(this);
68         final JTableHeader resultSetTableHeader = resultSetTable.getTableHeader();
69         final ConsoleTextArea textArea = new ConsoleTextArea(this);
70         this.op = new WindowOutputProcessor(this, resultSetTable, textArea);
71         this.menu = new Menu(this);
72         this.panel1 = new JPanel(new BorderLayout());
73         this.panel2 = new JPanel(new BorderLayout());
74         this.split1 = split1;
75         this.split2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
76         this.resultSetTable = resultSetTable;
77         this.textArea = textArea;
78         this.infoTree = infoTree;
79         this.textSearchPanel = new TextSearchPanel(op);
80         this.statusBar = new JLabel(" ");
81         this.historyList = new LinkedList<String>();
82         this.historyIndex = 0;
83         this.executorService = Executors.newScheduledThreadPool(3,
84                                                                 DaemonThreadFactory.getInstance());
85         // [Components]
86         // OutputProcessor as frame
87         op.setTitle(res.get(".title"));
88         op.setIconImage(Utilities.getImageIcon("stew.png").getImage());
89         op.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
90         // splitpane (infotree and sub-splitpane)
91         split1.setResizeWeight(0.6f);
92         split1.setDividerSize(4);
93         // splitpane (table and textarea)
94         split2.setOrientation(VERTICAL_SPLIT);
95         split2.setDividerSize(6);
96         split2.setResizeWeight(0.6f);
97         // text area
98         textArea.setMargin(new Insets(4, 8, 4, 4));
99         textArea.setLineWrap(true);
100         textArea.setWrapStyleWord(false);
101         // text search
102         this.textSearchMap = new LinkedHashMap<JComponent, TextSearch>();
103         final TextSearchPanel textSearchPanel = this.textSearchPanel;
104         final Map<JComponent, TextSearch> textSearchMap = this.textSearchMap;
105         textSearchMap.put(infoTree, infoTree);
106         textSearchMap.put(resultSetTable, resultSetTable);
107         textSearchMap.put(resultSetTableHeader,
108                           new ResultSetTable.TableHeaderTextSearch(resultSetTable,
109                                                                    resultSetTableHeader));
110         textSearchMap.put(textArea, textArea);
111         for (Entry<JComponent, TextSearch> entry : textSearchMap.entrySet()) {
112             final JComponent c = entry.getKey();
113             c.addFocusListener(new FocusAdapter() {
114                 @Override
115                 public void focusGained(FocusEvent e) {
116                     focused = c;
117                     textSearchPanel.setCurrentTarget(textSearchMap.get(c));
118                 }
119             });
120             textSearchPanel.addTarget(entry.getValue());
121         }
122         // status bar
123         statusBar.setForeground(Color.BLUE);
124         // [Layouts]
125         /*
126          * split2 = ResultSetTable + TextArea 
127          * +----------------------------+
128          * | split2                     |
129          * | +------------------------+ |
130          * | | scroll(resultSetTable) | |
131          * | +------------------------+ |
132          * | +------------------------+ |
133          * | | panel2                 | |
134          * | | +--------------------+ | |
135          * | | | scroll(textArea)   | | |
136          * | | +--------------------+ | |
137          * | | | textSearchPanel    | | |
138          * | | +--------------------+ | |
139          * | +------------------------+ |
140          * +----------------------------+
141          * when DatabaseInfoTree is visible
142          * +-----------------------------------+
143          * | panel1                            |
144          * | +-------------------------------+ |
145          * | | split1                        | |
146          * | | +------------+ +------------+ | |
147          * | | | scroll     | | split2     | | |
148          * | | | (infoTree) | |            | | |
149          * | | +------------+ +------------+ | |
150          * | +-------------------------------+ |
151          * +-----------------------------------+
152          * | status bar                        |
153          * +-----------------------------------+
154          * when DatabaseInfoTree is not visible
155          * +-----------------------------------+
156          * | panel1                            |
157          * | +-------------------------------+ |
158          * | | split2                        | |
159          * | +-------------------------------+ |
160          * +-----------------------------------+
161          * | status bar                        |
162          * +-----------------------------------+
163          */
164         panel2.add(new JScrollPane(textArea, VERTICAL_SCROLLBAR_ALWAYS, HORIZONTAL_SCROLLBAR_NEVER),
165                    BorderLayout.CENTER);
166         panel2.add(textSearchPanel, BorderLayout.SOUTH);
167         split2.setTopComponent(new JScrollPane(resultSetTable));
168         split2.setBottomComponent(panel2);
169         op.add(panel1, BorderLayout.CENTER);
170         op.add(statusBar, BorderLayout.PAGE_END);
171         op.setJMenuBar(menu);
172         // [Restores Configs]
173         op.addPropertyChangeListener(menu);
174         infoTree.addPropertyChangeListener(menu);
175         resultSetTable.addPropertyChangeListener(menu);
176         statusBar.addPropertyChangeListener(menu);
177         loadConfiguration();
178         op.removePropertyChangeListener(menu);
179         infoTree.removePropertyChangeListener(menu);
180         resultSetTable.removePropertyChangeListener(menu);
181         // XXX cannot restore config of status-bar at following code
182         // statusBar.removePropertyChangeListener(menu);
183         // [Events]
184         op.addWindowListener(new WindowAdapter() {
185             @Override
186             public void windowClosing(WindowEvent e) {
187                 requestClose();
188             }
189         });
190         ContextMenu.create(infoTree, infoTree);
191         ContextMenu.create(resultSetTable);
192         ContextMenu.create(resultSetTable.getRowHeader(), resultSetTable, "ResultSetTable");
193         ContextMenu.create(resultSetTableHeader, resultSetTable, "ResultSetTableColumnHeader");
194         ContextMenu.createForText(textArea);
195     }
196
197     @Override
198     public void launch(Environment env) {
199         this.env = env;
200         op.setEnvironment(env);
201         op.setVisible(true);
202         op.output(new Prompt(env));
203         textArea.requestFocus();
204     }
205
206     @Override
207     public void run() {
208         Thread.setDefaultUncaughtExceptionHandler(this);
209         invoke(this);
210     }
211
212     @Override
213     public void uncaughtException(Thread t, Throwable e) {
214         log.fatal(e, "%s", t);
215         op.showErrorDialog(e);
216     }
217
218     @Override
219     public void anyActionPerformed(AnyActionEvent ev) {
220         log.atEnter("anyActionPerformed", ev);
221         ev.validate();
222         try {
223             resultSetTable.editingCanceled(new ChangeEvent(ev.getSource()));
224             final Object source = ev.getSource();
225             if (ev.isAnyOf(newWindow)) {
226                 invoke();
227             } else if (ev.isAnyOf(closeWindow)) {
228                 requestClose();
229             } else if (ev.isAnyOf(quit)) {
230                 requestExit();
231             } else if (ev.isAnyOf(showInfoTree)) {
232                 setInfoTreePaneVisibility(((JCheckBoxMenuItem)source).isSelected());
233                 op.validate();
234                 op.repaint();
235             } else if (ev.isAnyOf(cut, copy, paste, selectAll)) {
236                 if (focused != null) {
237                     final String cmd;
238                     if (ev.isAnyOf(cut)) {
239                         if (focused instanceof JTextComponent) {
240                             cmd = "cut-to-clipboard";
241                         } else {
242                             cmd = "";
243                         }
244                     } else if (ev.isAnyOf(copy)) {
245                         if (focused instanceof JTextComponent) {
246                             cmd = "copy-to-clipboard";
247                         } else {
248                             cmd = ev.getActionCommand();
249                         }
250                     } else if (ev.isAnyOf(paste)) {
251                         if (focused instanceof JTextComponent) {
252                             cmd = "paste-from-clipboard";
253                         } else if (focused instanceof DatabaseInfoTree) {
254                             cmd = "";
255                         } else {
256                             cmd = ev.getActionCommand();
257                         }
258                     } else if (ev.isAnyOf(selectAll)) {
259                         if (focused instanceof JTextComponent) {
260                             cmd = "select-all";
261                         } else {
262                             cmd = ev.getActionCommand();
263                         }
264                     } else {
265                         cmd = "";
266                     }
267                     if (cmd.length() == 0) {
268                         log.debug("no action: %s, cmd=%s",
269                                   focused.getClass(),
270                                   ev.getActionCommand());
271                     } else {
272                         final Action action = focused.getActionMap().get(cmd);
273                         log.debug("convert to plain Action Event: orig=%s", ev);
274                         action.actionPerformed(new ActionEvent(focused, ACTION_PERFORMED, cmd));
275                     }
276                 }
277             } else if (ev.isAnyOf(find)) {
278                 textSearchPanel.setCurrentTarget(textSearchMap.get(focused));
279                 textSearchPanel.setVisible(true);
280             } else if (ev.isAnyOf(toggleFocus)) {
281                 if (textArea.isFocusOwner()) {
282                     resultSetTable.requestFocus();
283                 } else {
284                     textArea.requestFocus();
285                 }
286             } else if (ev.isAnyOf(clearMessage)) {
287                 textArea.clear();
288                 executeCommand("");
289             } else if (ev.isAnyOf(showStatusBar)) {
290                 statusBar.setVisible(((JCheckBoxMenuItem)source).isSelected());
291             } else if (ev.isAnyOf(showColumnNumber)) {
292                 resultSetTable.anyActionPerformed(ev);
293             } else if (ev.isAnyOf(refresh)) {
294                 refreshResult();
295             } else if (ev.isAnyOf(autoAdjustModeNone,
296                                   autoAdjustModeHeader,
297                                   autoAdjustModeValue,
298                                   autoAdjustModeHeaderAndValue)) {
299                 resultSetTable.setAutoAdjustMode(ev.getActionCommand());
300             } else if (ev.isAnyOf(widenColumnWidth, narrowColumnWidth, adjustColumnWidth)) {
301                 resultSetTable.anyActionPerformed(ev);
302             } else if (ev.isAnyOf(executeCommand, execute)) {
303                 executeCommand(textArea.getEditableText());
304             } else if (ev.isAnyOf(breakCommand)) {
305                 env.getOutputProcessor().close();
306                 env.setOutputProcessor(new WindowOutputProcessor.Bypass(op));
307                 op.output(res.get("i.cancelled"));
308                 doPostProcess();
309             } else if (ev.isAnyOf(lastHistory)) {
310                 retrieveHistory(-1);
311             } else if (ev.isAnyOf(nextHistory)) {
312                 retrieveHistory(+1);
313             } else if (ev.isAnyOf(sendRollback)) {
314                 if (confirmCommitable()
315                     && showConfirmDialog(op, res.get("i.confirm-rollback"), null, OK_CANCEL_OPTION) == OK_OPTION) {
316                     executeCommand("rollback");
317                 }
318             } else if (ev.isAnyOf(sendCommit)) {
319                 if (confirmCommitable()
320                     && showConfirmDialog(op, res.get("i.confirm-commit"), null, OK_CANCEL_OPTION) == OK_OPTION) {
321                     executeCommand("commit");
322                 }
323             } else if (ev.isAnyOf(connect)) {
324                 env.updateConnectorMap();
325                 if (env.getConnectorMap().isEmpty()) {
326                     showMessageDialog(op, res.get("w.no-connector"));
327                     return;
328                 }
329                 Object[] a = ConnectorEntry.toList(env.getConnectorMap().values()).toArray();
330                 final String m = res.get("i.choose-connection");
331                 Object value = showInputDialog(op, m, null, PLAIN_MESSAGE, null, a, a[0]);
332                 if (value != null) {
333                     ConnectorEntry c = (ConnectorEntry)value;
334                     executeCommand("connect " + c.getId());
335                 }
336             } else if (ev.isAnyOf(disconnect)) {
337                 executeCommand("disconnect");
338             } else if (ev.isAnyOf(postProcessModeNone,
339                                   postProcessModeFocus,
340                                   postProcessModeShake,
341                                   postProcessModeBlink)) {
342                 op.setPostProcessMode(ev.getActionCommand());
343             } else if (ev.isAnyOf(inputEcryptionKey)) {
344                 editEncryptionKey();
345             } else if (ev.isAnyOf(editConnectors)) {
346                 editConnectorMap();
347             } else if (ev.isAnyOf(sortResult)) {
348                 resultSetTable.doSort(resultSetTable.getSelectedColumn());
349             } else if (ev.isAnyOf(importFile, exportFile, showAbout)) {
350                 op.anyActionPerformed(ev);
351             } else if (ev.isAnyOf(showHelp)) {
352                 showHelp();
353             } else if (ev.isAnyOf(ResultSetTable.ActionKey.findColumnName)) {
354                 resultSetTable.getTableHeader().requestFocus();
355                 textSearchPanel.setVisible(true);
356             } else if (ev.isAnyOf(ResultSetTable.ActionKey.jumpToColumn)) {
357                 resultSetTable.anyActionPerformed(ev);
358             } else if (ev.isAnyOf(ConsoleTextArea.ActionKey.insertText)) {
359                 textArea.anyActionPerformed(ev);
360             } else {
361                 log.warn("not expected: Event=%s", ev);
362             }
363         } catch (Exception ex) {
364             op.showErrorDialog(ex);
365         }
366         log.atExit("dispatch");
367     }
368
369     /**
370      * Controls visibility of DatabaseInfoTree pane.
371      * @param show
372      */
373     void setInfoTreePaneVisibility(boolean show) {
374         if (show) {
375             split1.removeAll();
376             split1.setTopComponent(new JScrollPane(infoTree));
377             split1.setBottomComponent(split2);
378             panel1.removeAll();
379             panel1.add(split1, BorderLayout.CENTER);
380             infoTree.setEnabled(true);
381             if (env != null) {
382                 try {
383                     infoTree.refreshRoot(env);
384                 } catch (SQLException ex) {
385                     log.error(ex);
386                     op.showErrorDialog(ex);
387                 }
388             }
389         } else {
390             infoTree.clear();
391             infoTree.setEnabled(false);
392             panel1.removeAll();
393             panel1.add(split2, BorderLayout.CENTER);
394         }
395         SwingUtilities.updateComponentTreeUI(op);
396     }
397
398     private void loadConfiguration() {
399         Configuration cnf = Configuration.load(Bootstrap.getDirectory());
400         op.setSize(cnf.getSize());
401         op.setLocation(cnf.getLocation());
402         split2.setDividerLocation(cnf.getDividerLocation());
403         statusBar.setVisible(cnf.isShowStatusBar());
404         resultSetTable.setShowColumnNumber(cnf.isShowTableColumnNumber());
405         split1.setDividerLocation(cnf.getDividerLocation0());
406         op.setAlwaysOnTop(cnf.isAlwaysOnTop());
407         resultSetTable.setAutoAdjustMode(cnf.getAutoAdjustMode());
408         op.setPostProcessMode(cnf.getPostProcessMode());
409         setInfoTreePaneVisibility(cnf.isShowInfoTree());
410         changeFont("monospaced", Font.PLAIN, 1.0d);
411     }
412
413     private void saveConfiguration() {
414         Configuration cnf = Configuration.load(Bootstrap.getDirectory());
415         if ((op.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0) {
416             // only not maximized
417             cnf.setSize(op.getSize());
418             cnf.setLocation(op.getLocation());
419             cnf.setDividerLocation(split2.getDividerLocation());
420             cnf.setDividerLocation0(split1.getDividerLocation());
421         }
422         cnf.setShowStatusBar(statusBar.isVisible());
423         cnf.setShowTableColumnNumber(resultSetTable.isShowColumnNumber());
424         cnf.setShowInfoTree(infoTree.isEnabled());
425         cnf.setAlwaysOnTop(op.isAlwaysOnTop());
426         cnf.setAutoAdjustMode(resultSetTable.getAutoAdjustMode());
427         cnf.setPostProcessMode(op.getPostProcessMode());
428         cnf.save();
429     }
430
431     /**
432      * Configuration (Bean) for saving and loading.
433      */
434     @SuppressWarnings("all")
435     public static final class Configuration {
436
437         private static final Logger log = Logger.getLogger(Configuration.class);
438
439         private transient File directory;
440
441         private Dimension size;
442         private Point location;
443         private int dividerLocation;
444         private int dividerLocation0;
445         private boolean showStatusBar;
446         private boolean showTableColumnNumber;
447         private boolean showInfoTree;
448         private boolean alwaysOnTop;
449         private String autoAdjustMode;
450         private String postProcessMode;
451
452         public Configuration() {
453             this.size = new Dimension(640, 480);
454             this.location = new Point(200, 200);
455             this.dividerLocation = -1;
456             this.dividerLocation0 = -1;
457             this.showStatusBar = false;
458             this.showTableColumnNumber = false;
459             this.showInfoTree = false;
460             this.alwaysOnTop = false;
461             this.autoAdjustMode = AnyActionKey.autoAdjustMode.toString();
462             this.postProcessMode = AnyActionKey.postProcessMode.toString();
463         }
464
465         void setDirectory(File directory) {
466             this.directory = directory;
467         }
468
469         void save() {
470             final File file = getFile(directory);
471             log.debug("save Configuration to: [%s]", file);
472             try {
473                 XMLEncoder encoder = new XMLEncoder(new FileOutputStream(file));
474                 try {
475                     encoder.writeObject(this);
476                 } finally {
477                     encoder.close();
478                 }
479             } catch (Exception ex) {
480                 log.warn(ex);
481             }
482         }
483
484         static Configuration load(File directory) {
485             final File file = getFile(directory);
486             log.debug("load Configuration from: [%s]", file);
487             if (file.exists()) {
488                 try {
489                     XMLDecoder decoder = new XMLDecoder(new FileInputStream(file));
490                     try {
491                         final Configuration instance = (Configuration)decoder.readObject();
492                         instance.setDirectory(directory);
493                         return instance;
494                     } finally {
495                         decoder.close();
496                     }
497                 } catch (Exception ex) {
498                     log.warn(ex);
499                 }
500             }
501             final Configuration instance = new Configuration();
502             instance.setDirectory(directory);
503             return instance;
504         }
505
506         private static File getFile(File systemDirectory) {
507             final File file = new File(systemDirectory, Configuration.class.getName() + ".xml");
508             return file.getAbsoluteFile();
509         }
510
511         public Dimension getSize() {
512             return size;
513         }
514
515         public void setSize(Dimension size) {
516             this.size = size;
517         }
518
519         public Point getLocation() {
520             return location;
521         }
522
523         public void setLocation(Point location) {
524             this.location = location;
525         }
526
527         public int getDividerLocation() {
528             return dividerLocation;
529         }
530
531         public void setDividerLocation(int dividerLocation) {
532             this.dividerLocation = dividerLocation;
533         }
534
535         public int getDividerLocation0() {
536             return dividerLocation0;
537         }
538
539         public void setDividerLocation0(int dividerLocation0) {
540             this.dividerLocation0 = dividerLocation0;
541         }
542
543         public boolean isShowStatusBar() {
544             return showStatusBar;
545         }
546
547         public void setShowStatusBar(boolean showStatusBar) {
548             this.showStatusBar = showStatusBar;
549         }
550
551         public boolean isShowTableColumnNumber() {
552             return showTableColumnNumber;
553         }
554
555         public void setShowTableColumnNumber(boolean showTableColumnNumber) {
556             this.showTableColumnNumber = showTableColumnNumber;
557         }
558
559         public boolean isShowInfoTree() {
560             return showInfoTree;
561         }
562
563         public void setShowInfoTree(boolean showInfoTree) {
564             this.showInfoTree = showInfoTree;
565         }
566
567         public boolean isAlwaysOnTop() {
568             return alwaysOnTop;
569         }
570
571         public void setAlwaysOnTop(boolean alwaysOnTop) {
572             this.alwaysOnTop = alwaysOnTop;
573         }
574
575         public String getAutoAdjustMode() {
576             return autoAdjustMode;
577         }
578
579         public void setAutoAdjustMode(String autoAdjustMode) {
580             this.autoAdjustMode = autoAdjustMode;
581         }
582
583         public String getPostProcessMode() {
584             return postProcessMode;
585         }
586
587         public void setPostProcessMode(String postProcessMode) {
588             this.postProcessMode = postProcessMode;
589         }
590
591     }
592
593     private void changeFont(String family, int style, double sizeRate) {
594         FontControlLookAndFeel.change(family, style, sizeRate);
595         SwingUtilities.updateComponentTreeUI(op);
596         Font newfont = textArea.getFont();
597         if (newfont != null) {
598             statusBar.setFont(newfont.deriveFont(newfont.getSize() * 0.8f));
599         }
600     }
601
602     void handleError(Throwable th) {
603         log.error(th);
604         op.showErrorDialog(th);
605     }
606
607     static void invoke() {
608         invoke(new WindowLauncher());
609     }
610
611     static void invoke(WindowLauncher instance) {
612         final Environment env = new Environment();
613         env.setOutputProcessor(new WindowOutputProcessor.Bypass(instance.op));
614         instance.launch(env);
615     }
616
617     /**
618      * Starts to exit application.
619      */
620     static void exit() {
621         for (WindowLauncher instance : new ArrayList<WindowLauncher>(instances)) {
622             try {
623                 instance.close();
624             } catch (Exception ex) {
625                 log.warn(ex, "error occurred when closing all instances");
626             }
627         }
628     }
629
630     /**
631      * Closes this window.
632      */
633     void close() {
634         instances.remove(this);
635         try {
636             env.release();
637             saveConfiguration();
638             executorService.shutdown();
639         } finally {
640             op.dispose();
641         }
642     }
643
644     /**
645      * Confirms whether pressed YES or not at dialog.
646      * @param message
647      * @return true if pressed YES
648      */
649     private boolean confirmYes(String message) {
650         return showConfirmDialog(op, message, "", YES_NO_OPTION) == YES_OPTION;
651     }
652
653     private boolean confirmCommitable() {
654         if (env.getCurrentConnection() == null) {
655             showMessageDialog(op, res.get("w.not-connect"), null, OK_OPTION);
656             return false;
657         }
658         if (env.getCurrentConnector().isReadOnly()) {
659             showMessageDialog(op, res.get("w.connector-readonly"), null, OK_OPTION);
660             return false;
661         }
662         return true;
663     }
664
665     private void retrieveHistory(int value) {
666         if (historyList.isEmpty()) {
667             return;
668         }
669         historyIndex += value;
670         if (historyIndex >= historyList.size()) {
671             historyIndex = 0;
672         } else if (historyIndex < 0) {
673             historyIndex = historyList.size() - 1;
674         }
675         textArea.replace(historyList.get(historyIndex));
676         final int endPosition = textArea.getEndPosition();
677         textArea.setSelectionStart(endPosition);
678         textArea.moveCaretPosition(endPosition);
679         textArea.requestFocus();
680     }
681
682     /**
683      * Requests to close this window.
684      */
685     void requestClose() {
686         if (instances.size() == 1) {
687             requestExit();
688         } else if (env.getCurrentConnection() == null || confirmYes(res.get("i.confirm-close"))) {
689             close();
690         }
691     }
692
693     /**
694      * Requests to exit this application.
695      */
696     void requestExit() {
697         if (confirmYes(res.get("i.confirm-quit"))) {
698             exit();
699         }
700     }
701
702     private void refreshResult() {
703         if (resultSetTable.getModel() instanceof ResultSetTableModel) {
704             ResultSetTableModel m = resultSetTable.getResultSetTableModel();
705             if (m.isSameConnection(env.getCurrentConnection())) {
706                 final String s = m.getCommandString();
707                 if (s != null && s.length() > 0) {
708                     executeCommand(s);
709                 }
710             }
711         }
712     }
713
714     private void editEncryptionKey() {
715         JPasswordField password = new JPasswordField(20);
716         Object[] a = {res.get("i.input-encryption-key"), password};
717         if (showConfirmDialog(op, a, null, OK_CANCEL_OPTION) == OK_OPTION) {
718             CipherPassword.setSecretKey(String.valueOf(password.getPassword()));
719         }
720     }
721
722     private void editConnectorMap() {
723         env.updateConnectorMap();
724         if (env.getCurrentConnector() != null) {
725             showMessageDialog(op, res.get("i.reconnect-after-edited-current-connector"));
726         }
727         ConnectorMapEditDialog dialog = new ConnectorMapEditDialog(op, env);
728         dialog.pack();
729         dialog.setModal(true);
730         dialog.setLocationRelativeTo(op);
731         dialog.setVisible(true);
732         env.updateConnectorMap();
733     }
734
735     private static void showHelp() {
736         final String htmlFileName = "MANUAL_ja.html";
737         if (Desktop.isDesktopSupported()) {
738             Desktop desktop = Desktop.getDesktop();
739             if (desktop.isSupported(Desktop.Action.OPEN)) {
740                 File file = new File(htmlFileName);
741                 if (file.exists()) {
742                     try {
743                         desktop.open(file);
744                     } catch (IOException ex) {
745                         throw new RuntimeException(ex);
746                     }
747                 }
748             }
749         } else {
750             final String msg = res.get("e.cannot-open-help-automatically", htmlFileName);
751             WindowOutputProcessor.showInformationMessageDialog(getRootFrame(), msg, "");
752         }
753     }
754
755     /**
756      * Executes a command.
757      * @param commandString
758      */
759     void executeCommand(String commandString) {
760         assert commandString != null;
761         if (!commandString.equals(textArea.getEditableText())) {
762             textArea.replace(commandString);
763         }
764         op.output("");
765         if (commandString.trim().length() == 0) {
766             doPostProcess();
767         } else {
768             final String cmd = commandString;
769             final Environment env = this.env;
770             final DatabaseInfoTree infoTree = this.infoTree;
771             final JLabel statusBar = this.statusBar;
772             final OutputProcessor opref = env.getOutputProcessor();
773             try {
774                 doPreProcess();
775                 executorService.execute(new Runnable() {
776                     @Override
777                     public void run() {
778                         Connection conn = env.getCurrentConnection();
779                         long time = System.currentTimeMillis();
780                         if (!Command.invoke(env, cmd)) {
781                             exit();
782                         }
783                         if (infoTree.isEnabled()) {
784                             try {
785                                 if (env.getCurrentConnection() != conn) {
786                                     infoTree.clear();
787                                     if (env.getCurrentConnection() != null) {
788                                         infoTree.refreshRoot(env);
789                                     }
790                                 }
791                             } catch (Throwable th) {
792                                 handleError(th);
793                             }
794                         }
795                         if (env.getOutputProcessor() == opref) {
796                             time = System.currentTimeMillis() - time;
797                             statusBar.setText(res.get("i.statusbar-message", time / 1000f, cmd));
798                             AnyAction invoker = new AnyAction(this);
799                             invoker.doLater("callDoPostProcess");
800                         }
801                     }
802                     @SuppressWarnings("unused")
803                     void callDoPostProcess() {
804                         WindowLauncher.this.doPostProcess();
805                     }
806                 });
807             } catch (Exception ex) {
808                 throw new RuntimeException(ex);
809             } finally {
810                 historyIndex = historyList.size();
811             }
812             if (historyList.contains(commandString)) {
813                 historyList.remove(commandString);
814             }
815             historyList.add(commandString);
816         }
817         historyIndex = historyList.size();
818     }
819
820     void doPreProcess() {
821         ((Menu)op.getJMenuBar()).setEnabledStates(true);
822         resultSetTable.setEnabled(false);
823         textArea.setEnabled(false);
824         op.repaint();
825     }
826
827     void doPostProcess() {
828         ((Menu)op.getJMenuBar()).setEnabledStates(false);
829         resultSetTable.setEnabled(true);
830         textArea.setEnabled(true);
831         op.output(new Prompt(env));
832         op.doPostProcess();
833     }
834
835     static void wakeup() {
836         for (WindowLauncher instance : new ArrayList<WindowLauncher>(instances)) {
837             try {
838                 SwingUtilities.updateComponentTreeUI(instance.op);
839             } catch (Exception ex) {
840                 log.warn(ex);
841             }
842         }
843         log.info("wake up");
844     }
845
846     private static final class WakeupTimerTask extends TimerTask {
847         private final AnyAction aa = new AnyAction(this);
848         @Override
849         public void run() {
850             aa.doLater("callWakeup");
851         }
852         @SuppressWarnings("unused")
853         void callWakeup() {
854             wakeup();
855         }
856     }
857
858     /**
859      * (entry point)
860      * @param args
861      */
862     public static void main(String... args) {
863         final int residentCycle = Bootstrap.getPropertyAsInt("net.argius.stew.ui.window.resident",
864                                                              0);
865         if (residentCycle > 0) {
866             final long msec = residentCycle * 60000L;
867             Timer timer = new Timer(true);
868             timer.scheduleAtFixedRate(new WakeupTimerTask(), msec, msec);
869         }
870         EventQueue.invokeLater(new WindowLauncher());
871     }
872
873 }