OSDN Git Service

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