OSDN Git Service

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