OSDN Git Service

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