OSDN Git Service

refactoring: the event handler of PostProcess was replaced Runnable with AnyAction
[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         ContextMenu.create(infoTree, infoTree);
185         ContextMenu.create(resultSetTable);
186         ContextMenu.create(resultSetTable.getRowHeader(), resultSetTable, "ResultSetTable");
187         ContextMenu.create(resultSetTableHeader, resultSetTable, "ResultSetTableColumnHeader");
188         ContextMenu.createForText(textArea);
189     }
190
191     @Override
192     public void launch(Environment env) {
193         this.env = env;
194         op.setEnvironment(env);
195         op.setVisible(true);
196         op.output(new Prompt(env));
197         textArea.requestFocus();
198     }
199
200     @Override
201     public void run() {
202         Thread.setDefaultUncaughtExceptionHandler(this);
203         invoke(this);
204     }
205
206     @Override
207     public void uncaughtException(Thread t, Throwable e) {
208         log.fatal(e, "%s", t);
209         op.showErrorDialog(e);
210     }
211
212     @Override
213     public void anyActionPerformed(AnyActionEvent ev) {
214         log.atEnter("anyActionPerformed", ev);
215         ev.validate();
216         try {
217             resultSetTable.editingCanceled(new ChangeEvent(ev.getSource()));
218             final Object source = ev.getSource();
219             if (ev.isAnyOf(newWindow)) {
220                 invoke();
221             } else if (ev.isAnyOf(closeWindow)) {
222                 requestClose();
223             } else if (ev.isAnyOf(quit)) {
224                 requestExit();
225             } else if (ev.isAnyOf(showInfoTree)) {
226                 setInfoTreePaneVisibility(((JCheckBoxMenuItem)source).isSelected());
227                 op.validate();
228                 op.repaint();
229             } else if (ev.isAnyOf(cut, copy, paste, selectAll)) {
230                 if (focused != null) {
231                     final String cmd;
232                     if (ev.isAnyOf(cut)) {
233                         if (focused instanceof JTextComponent) {
234                             cmd = "cut-to-clipboard";
235                         } else {
236                             cmd = "";
237                         }
238                     } else if (ev.isAnyOf(copy)) {
239                         if (focused instanceof JTextComponent) {
240                             cmd = "copy-to-clipboard";
241                         } else {
242                             cmd = ev.getActionCommand();
243                         }
244                     } else if (ev.isAnyOf(paste)) {
245                         if (focused instanceof JTextComponent) {
246                             cmd = "paste-from-clipboard";
247                         } else if (focused instanceof DatabaseInfoTree) {
248                             cmd = "";
249                         } else {
250                             cmd = ev.getActionCommand();
251                         }
252                     } else if (ev.isAnyOf(selectAll)) {
253                         if (focused instanceof JTextComponent) {
254                             cmd = "select-all";
255                         } else {
256                             cmd = ev.getActionCommand();
257                         }
258                     } else {
259                         cmd = "";
260                     }
261                     if (cmd.length() == 0) {
262                         log.debug("no action: %s, cmd=%s",
263                                   focused.getClass(),
264                                   ev.getActionCommand());
265                     } else {
266                         final Action action = focused.getActionMap().get(cmd);
267                         log.debug("convert to plain Action Event: orig=%s", ev);
268                         action.actionPerformed(new ActionEvent(focused, ACTION_PERFORMED, cmd));
269                     }
270                 }
271             } else if (ev.isAnyOf(find)) {
272                 textSearchPanel.setCurrentTarget(textSearchMap.get(focused));
273                 textSearchPanel.setVisible(true);
274             } else if (ev.isAnyOf(toggleFocus)) {
275                 if (textArea.isFocusOwner()) {
276                     resultSetTable.requestFocus();
277                 } else {
278                     textArea.requestFocus();
279                 }
280             } else if (ev.isAnyOf(clearMessage)) {
281                 textArea.clear();
282                 executeCommand("");
283             } else if (ev.isAnyOf(showStatusBar)) {
284                 statusBar.setVisible(((JCheckBoxMenuItem)source).isSelected());
285             } else if (ev.isAnyOf(showColumnNumber)) {
286                 resultSetTable.anyActionPerformed(ev);
287             } else if (ev.isAnyOf(refresh)) {
288                 refreshResult();
289             } else if (ev.isAnyOf(autoAdjustModeNone,
290                                   autoAdjustModeHeader,
291                                   autoAdjustModeValue,
292                                   autoAdjustModeHeaderAndValue)) {
293                 resultSetTable.setAutoAdjustMode(ev.getActionCommand());
294             } else if (ev.isAnyOf(widenColumnWidth, narrowColumnWidth, adjustColumnWidth)) {
295                 resultSetTable.anyActionPerformed(ev);
296             } else if (ev.isAnyOf(executeCommand, execute)) {
297                 executeCommand(textArea.getEditableText());
298             } else if (ev.isAnyOf(breakCommand)) {
299                 env.getOutputProcessor().close();
300                 env.setOutputProcessor(new WindowOutputProcessor.Bypass(op));
301                 op.output(res.get("i.cancelled"));
302                 doPostProcess();
303             } else if (ev.isAnyOf(lastHistory)) {
304                 retrieveHistory(-1);
305             } else if (ev.isAnyOf(nextHistory)) {
306                 retrieveHistory(+1);
307             } else if (ev.isAnyOf(sendRollback)) {
308                 if (confirmCommitable()
309                     && showConfirmDialog(op, res.get("i.confirm-rollback"), null, OK_CANCEL_OPTION) == OK_OPTION) {
310                     executeCommand("rollback");
311                 }
312             } else if (ev.isAnyOf(sendCommit)) {
313                 if (confirmCommitable()
314                     && showConfirmDialog(op, res.get("i.confirm-commit"), null, OK_CANCEL_OPTION) == OK_OPTION) {
315                     executeCommand("commit");
316                 }
317             } else if (ev.isAnyOf(connect)) {
318                 env.updateConnectorMap();
319                 if (env.getConnectorMap().isEmpty()) {
320                     showMessageDialog(op, res.get("w.no-connector"));
321                     return;
322                 }
323                 Object[] a = ConnectorEntry.toList(env.getConnectorMap().values()).toArray();
324                 final String m = res.get("i.choose-connection");
325                 Object value = showInputDialog(op, m, null, PLAIN_MESSAGE, null, a, a[0]);
326                 if (value != null) {
327                     ConnectorEntry c = (ConnectorEntry)value;
328                     executeCommand("connect " + c.getId());
329                 }
330             } else if (ev.isAnyOf(disconnect)) {
331                 executeCommand("disconnect");
332             } else if (ev.isAnyOf(postProcessModeNone,
333                                   postProcessModeFocus,
334                                   postProcessModeShake,
335                                   postProcessModeBlink)) {
336                 op.setPostProcessMode(ev.getActionCommand());
337             } else if (ev.isAnyOf(inputEcryptionKey)) {
338                 editEncryptionKey();
339             } else if (ev.isAnyOf(editConnectors)) {
340                 editConnectorMap();
341             } else if (ev.isAnyOf(sortResult)) {
342                 resultSetTable.doSort(resultSetTable.getSelectedColumn());
343             } else if (ev.isAnyOf(importFile, exportFile, showAbout)) {
344                 op.anyActionPerformed(ev);
345             } else if (ev.isAnyOf(showHelp)) {
346                 showHelp();
347             } else if (ev.isAnyOf(ResultSetTable.ActionKey.findColumnName)) {
348                 resultSetTable.getTableHeader().requestFocus();
349                 textSearchPanel.setVisible(true);
350             } else if (ev.isAnyOf(ResultSetTable.ActionKey.jumpToColumn)) {
351                 resultSetTable.anyActionPerformed(ev);
352             } else if (ev.isAnyOf(ConsoleTextArea.ActionKey.insertText)) {
353                 textArea.anyActionPerformed(ev);
354             } else {
355                 log.warn("not expected: Event=%s", ev);
356             }
357         } catch (Exception ex) {
358             op.showErrorDialog(ex);
359         }
360         log.atExit("dispatch");
361     }
362
363     /**
364      * Controls visibility of DatabaseInfoTree pane.
365      * @param show
366      */
367     void setInfoTreePaneVisibility(boolean show) {
368         if (show) {
369             split1.removeAll();
370             split1.setTopComponent(new JScrollPane(infoTree));
371             split1.setBottomComponent(split2);
372             panel1.removeAll();
373             panel1.add(split1, BorderLayout.CENTER);
374             infoTree.setEnabled(true);
375             if (env != null) {
376                 try {
377                     infoTree.refreshRoot(env);
378                 } catch (SQLException ex) {
379                     log.error(ex);
380                     op.showErrorDialog(ex);
381                 }
382             }
383         } else {
384             infoTree.clear();
385             infoTree.setEnabled(false);
386             panel1.removeAll();
387             panel1.add(split2, BorderLayout.CENTER);
388         }
389         SwingUtilities.updateComponentTreeUI(op);
390     }
391
392     private void loadConfiguration() {
393         Configuration cnf = Configuration.load();
394         op.setSize(cnf.getSize());
395         op.setLocation(cnf.getLocation());
396         split2.setDividerLocation(cnf.getDividerLocation());
397         statusBar.setVisible(cnf.isShowStatusBar());
398         resultSetTable.setShowColumnNumber(cnf.isShowTableColumnNumber());
399         split1.setDividerLocation(cnf.getDividerLocation0());
400         op.setAlwaysOnTop(cnf.isAlwaysOnTop());
401         resultSetTable.setAutoAdjustMode(cnf.getAutoAdjustMode());
402         op.setPostProcessMode(cnf.getPostProcessMode());
403         setInfoTreePaneVisibility(cnf.isShowInfoTree());
404         changeFont("monospaced", Font.PLAIN, 1.0d);
405     }
406
407     private void saveConfiguration() {
408         Configuration cnf = Configuration.load();
409         if ((op.getExtendedState() & Frame.MAXIMIZED_BOTH) == 0) {
410             // only not maximized
411             cnf.setSize(op.getSize());
412             cnf.setLocation(op.getLocation());
413             cnf.setDividerLocation(split2.getDividerLocation());
414             cnf.setDividerLocation0(split1.getDividerLocation());
415         }
416         cnf.setShowStatusBar(statusBar.isVisible());
417         cnf.setShowTableColumnNumber(resultSetTable.isShowColumnNumber());
418         cnf.setShowInfoTree(infoTree.isEnabled());
419         cnf.setAlwaysOnTop(op.isAlwaysOnTop());
420         cnf.setAutoAdjustMode(resultSetTable.getAutoAdjustMode());
421         cnf.setPostProcessMode(op.getPostProcessMode());
422         cnf.save();
423     }
424
425     /**
426      * Configuration (Bean) for saving and loading.
427      */
428     @SuppressWarnings("all")
429     public static final class Configuration {
430
431         private static final Logger log = Logger.getLogger(Configuration.class);
432
433         private Dimension size;
434         private Point location;
435         private int dividerLocation;
436         private int dividerLocation0;
437         private boolean showStatusBar;
438         private boolean showTableColumnNumber;
439         private boolean showInfoTree;
440         private boolean alwaysOnTop;
441         private String autoAdjustMode;
442         private String postProcessMode;
443
444         public Configuration() {
445             this.size = new Dimension(640, 480);
446             this.location = new Point(200, 200);
447             this.dividerLocation = -1;
448             this.dividerLocation0 = -1;
449             this.showStatusBar = false;
450             this.showTableColumnNumber = false;
451             this.showInfoTree = false;
452             this.alwaysOnTop = false;
453             this.autoAdjustMode = AnyActionKey.autoAdjustMode.toString();
454             this.postProcessMode = AnyActionKey.postProcessMode.toString();
455         }
456
457         void save() {
458             final File file = getFile();
459             log.debug("save Configuration to: [%s]", file.getAbsolutePath());
460             try {
461                 XMLEncoder encoder = new XMLEncoder(new FileOutputStream(file));
462                 try {
463                     encoder.writeObject(this);
464                 } finally {
465                     encoder.close();
466                 }
467             } catch (Exception ex) {
468                 log.warn(ex);
469             }
470         }
471
472         static Configuration load() {
473             final File file = getFile();
474             log.debug("load Configuration from: [%s]", file.getAbsolutePath());
475             if (file.exists()) {
476                 try {
477                     XMLDecoder decoder = new XMLDecoder(new FileInputStream(file));
478                     try {
479                         return (Configuration)decoder.readObject();
480                     } finally {
481                         decoder.close();
482                     }
483                 } catch (Exception ex) {
484                     log.warn(ex);
485                 }
486             }
487             return new Configuration();
488         }
489
490         private static File getFile() {
491             return Bootstrap.getSystemFile(Configuration.class.getName() + ".xml");
492         }
493
494         public Dimension getSize() {
495             return size;
496         }
497
498         public void setSize(Dimension size) {
499             this.size = size;
500         }
501
502         public Point getLocation() {
503             return location;
504         }
505
506         public void setLocation(Point location) {
507             this.location = location;
508         }
509
510         public int getDividerLocation() {
511             return dividerLocation;
512         }
513
514         public void setDividerLocation(int dividerLocation) {
515             this.dividerLocation = dividerLocation;
516         }
517
518         public int getDividerLocation0() {
519             return dividerLocation0;
520         }
521
522         public void setDividerLocation0(int dividerLocation0) {
523             this.dividerLocation0 = dividerLocation0;
524         }
525
526         public boolean isShowStatusBar() {
527             return showStatusBar;
528         }
529
530         public void setShowStatusBar(boolean showStatusBar) {
531             this.showStatusBar = showStatusBar;
532         }
533
534         public boolean isShowTableColumnNumber() {
535             return showTableColumnNumber;
536         }
537
538         public void setShowTableColumnNumber(boolean showTableColumnNumber) {
539             this.showTableColumnNumber = showTableColumnNumber;
540         }
541
542         public boolean isShowInfoTree() {
543             return showInfoTree;
544         }
545
546         public void setShowInfoTree(boolean showInfoTree) {
547             this.showInfoTree = showInfoTree;
548         }
549
550         public boolean isAlwaysOnTop() {
551             return alwaysOnTop;
552         }
553
554         public void setAlwaysOnTop(boolean alwaysOnTop) {
555             this.alwaysOnTop = alwaysOnTop;
556         }
557
558         public String getAutoAdjustMode() {
559             return autoAdjustMode;
560         }
561
562         public void setAutoAdjustMode(String autoAdjustMode) {
563             this.autoAdjustMode = autoAdjustMode;
564         }
565
566         public String getPostProcessMode() {
567             return postProcessMode;
568         }
569
570         public void setPostProcessMode(String postProcessMode) {
571             this.postProcessMode = postProcessMode;
572         }
573
574     }
575
576     private void changeFont(String family, int style, double sizeRate) {
577         FontControlLookAndFeel.change(family, style, sizeRate);
578         SwingUtilities.updateComponentTreeUI(op);
579         Font newfont = textArea.getFont();
580         if (newfont != null) {
581             statusBar.setFont(newfont.deriveFont(newfont.getSize() * 0.8f));
582         }
583     }
584
585     void handleError(Throwable th) {
586         log.error(th);
587         op.showErrorDialog(th);
588     }
589
590     static void invoke() {
591         invoke(new WindowLauncher());
592     }
593
594     static void invoke(WindowLauncher instance) {
595         final Environment env = new Environment();
596         env.setOutputProcessor(new WindowOutputProcessor.Bypass(instance.op));
597         instance.launch(env);
598     }
599
600     /**
601      * Starts to exit application.
602      */
603     static void exit() {
604         for (WindowLauncher instance : new ArrayList<WindowLauncher>(instances)) {
605             try {
606                 instance.close();
607             } catch (Exception ex) {
608                 log.warn(ex, "error occurred when closing all instances");
609             }
610         }
611     }
612
613     /**
614      * Closes this window.
615      */
616     void close() {
617         instances.remove(this);
618         try {
619             env.release();
620             saveConfiguration();
621             executorService.shutdown();
622         } finally {
623             op.dispose();
624         }
625     }
626
627     /**
628      * Confirms whether pressed YES or not at dialog.
629      * @param message
630      * @return true if pressed YES
631      */
632     private boolean confirmYes(String message) {
633         return showConfirmDialog(op, message, "", YES_NO_OPTION) == YES_OPTION;
634     }
635
636     private boolean confirmCommitable() {
637         if (env.getCurrentConnection() == null) {
638             showMessageDialog(op, res.get("w.not-connect"), null, OK_OPTION);
639             return false;
640         }
641         if (env.getCurrentConnector().isReadOnly()) {
642             showMessageDialog(op, res.get("w.connector-readonly"), null, OK_OPTION);
643             return false;
644         }
645         return true;
646     }
647
648     private void retrieveHistory(int value) {
649         if (historyList.isEmpty()) {
650             return;
651         }
652         historyIndex += value;
653         if (historyIndex >= historyList.size()) {
654             historyIndex = 0;
655         } else if (historyIndex < 0) {
656             historyIndex = historyList.size() - 1;
657         }
658         textArea.replace(historyList.get(historyIndex));
659         final int endPosition = textArea.getEndPosition();
660         textArea.setSelectionStart(endPosition);
661         textArea.moveCaretPosition(endPosition);
662         textArea.requestFocus();
663     }
664
665     /**
666      * Requests to close this window.
667      */
668     void requestClose() {
669         if (instances.size() == 1) {
670             requestExit();
671         } else if (env.getCurrentConnection() == null || confirmYes(res.get("i.confirm-close"))) {
672             close();
673         }
674     }
675
676     /**
677      * Requests to exit this application.
678      */
679     void requestExit() {
680         if (confirmYes(res.get("i.confirm-quit"))) {
681             exit();
682         }
683     }
684
685     private void refreshResult() {
686         if (resultSetTable.getModel() instanceof ResultSetTableModel) {
687             ResultSetTableModel m = resultSetTable.getResultSetTableModel();
688             if (m.isSameConnection(env.getCurrentConnection())) {
689                 final String s = m.getCommandString();
690                 if (s != null && s.length() > 0) {
691                     executeCommand(s);
692                 }
693             }
694         }
695     }
696
697     private void editEncryptionKey() {
698         JPasswordField password = new JPasswordField(20);
699         Object[] a = {res.get("i.input-encryption-key"), password};
700         if (showConfirmDialog(op, a, null, OK_CANCEL_OPTION) == OK_OPTION) {
701             CipherPassword.setSecretKey(String.valueOf(password.getPassword()));
702         }
703     }
704
705     private void editConnectorMap() {
706         env.updateConnectorMap();
707         if (env.getCurrentConnector() != null) {
708             showMessageDialog(op, res.get("i.reconnect-after-edited-current-connector"));
709         }
710         ConnectorMapEditDialog dialog = new ConnectorMapEditDialog(op, env);
711         dialog.pack();
712         dialog.setModal(true);
713         dialog.setLocationRelativeTo(op);
714         dialog.setVisible(true);
715         env.updateConnectorMap();
716     }
717
718     private static void showHelp() {
719         final String htmlFileName = "MANUAL_ja.html";
720         if (Desktop.isDesktopSupported()) {
721             Desktop desktop = Desktop.getDesktop();
722             if (desktop.isSupported(Desktop.Action.OPEN)) {
723                 File file = new File(htmlFileName);
724                 if (file.exists()) {
725                     try {
726                         desktop.open(file);
727                     } catch (IOException ex) {
728                         throw new RuntimeException(ex);
729                     }
730                 }
731             }
732         } else {
733             final String msg = res.get("e.cannot-open-help-automatically", htmlFileName);
734             WindowOutputProcessor.showInformationMessageDialog(getRootFrame(), msg, "");
735         }
736     }
737
738     /**
739      * Executes a command.
740      * @param commandString
741      */
742     void executeCommand(String commandString) {
743         assert commandString != null;
744         if (!commandString.equals(textArea.getEditableText())) {
745             textArea.replace(commandString);
746         }
747         op.output("");
748         if (commandString.trim().length() == 0) {
749             doPostProcess();
750         } else {
751             final String cmd = commandString;
752             final Environment env = this.env;
753             final DatabaseInfoTree infoTree = this.infoTree;
754             final JLabel statusBar = this.statusBar;
755             final OutputProcessor opref = env.getOutputProcessor();
756             try {
757                 doPreProcess();
758                 executorService.execute(new Runnable() {
759                     @Override
760                     public void run() {
761                         Connection conn = env.getCurrentConnection();
762                         long time = System.currentTimeMillis();
763                         if (!Command.invoke(env, cmd)) {
764                             exit();
765                         }
766                         if (infoTree.isEnabled()) {
767                             try {
768                                 if (env.getCurrentConnection() != conn) {
769                                     infoTree.clear();
770                                     if (env.getCurrentConnection() != null) {
771                                         infoTree.refreshRoot(env);
772                                     }
773                                 }
774                             } catch (Throwable th) {
775                                 handleError(th);
776                             }
777                         }
778                         if (env.getOutputProcessor() == opref) {
779                             time = System.currentTimeMillis() - time;
780                             statusBar.setText(res.get("i.statusbar-message", time / 1000f, cmd));
781                             AnyAction invoker = new AnyAction(this);
782                             invoker.doLater("callDoPostProcess");
783                         }
784                     }
785                     @SuppressWarnings("unused")
786                     void callDoPostProcess() {
787                         WindowLauncher.this.doPostProcess();
788                     }
789                 });
790             } catch (Exception ex) {
791                 throw new RuntimeException(ex);
792             } finally {
793                 historyIndex = historyList.size();
794             }
795             if (historyList.contains(commandString)) {
796                 historyList.remove(commandString);
797             }
798             historyList.add(commandString);
799         }
800         historyIndex = historyList.size();
801     }
802
803     void doPreProcess() {
804         ((Menu)op.getJMenuBar()).setEnabledStates(true);
805         resultSetTable.setEnabled(false);
806         textArea.setEnabled(false);
807         op.repaint();
808     }
809
810     void doPostProcess() {
811         ((Menu)op.getJMenuBar()).setEnabledStates(false);
812         resultSetTable.setEnabled(true);
813         textArea.setEnabled(true);
814         op.output(new Prompt(env));
815         op.doPostProcess();
816     }
817
818     static void wakeup() {
819         for (WindowLauncher instance : new ArrayList<WindowLauncher>(instances)) {
820             try {
821                 SwingUtilities.updateComponentTreeUI(instance.op);
822             } catch (Exception ex) {
823                 log.warn(ex);
824             }
825         }
826         log.info("wake up");
827     }
828
829     private static final class WakeupTimerTask extends TimerTask {
830         private final AnyAction aa = new AnyAction(this);
831         @Override
832         public void run() {
833             aa.doLater("callWakeup");
834         }
835         @SuppressWarnings("unused")
836         void callWakeup() {
837             wakeup();
838         }
839     }
840
841     /**
842      * (entry point)
843      * @param args
844      */
845     public static void main(String... args) {
846         final int residentCycle = Bootstrap.getPropertyAsInt("net.argius.stew.ui.window.resident",
847                                                              0);
848         if (residentCycle > 0) {
849             final long msec = residentCycle * 60000L;
850             Timer timer = new Timer(true);
851             timer.scheduleAtFixedRate(new WakeupTimerTask(), msec, msec);
852         }
853         EventQueue.invokeLater(new WindowLauncher());
854     }
855
856 }