OSDN Git Service

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