001: /*
002: ** $Id: MainController.java,v 1.19 2000/11/07 19:54:09 mrw Exp $
003: **
004: ** Controller for the main view. The view container a menubar, toolbar
005: ** and a tab sheet. This controller responds to menu and toolbar events
006: ** from the view.
007: **
008: ** Mike Wilson, July 2000, mrw@whisperingwind.co.uk
009: **
010: ** (C) Copyright 2000, Mike Wilson, Reading, Berkshire, UK
011: **
012: ** This program is free software; you can redistribute it and/or modify
013: ** it under the terms of the GNU General Public License as published by
014: ** the Free Software Foundation; either version 2 of the License, or
015: ** (at your option) any later version.
016: **
017: ** This program is distributed in the hope that it will be useful,
018: ** but WITHOUT ANY WARRANTY; without even the implied warranty of
019: ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
020: ** GNU General Public License for more details.
021: **
022: ** You should have received a copy of the GNU Library General
023: ** Public License along with this library; if not, write to the
024: ** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
025: ** Boston, MA 02111-1307 USA.
026: */
027:
028: package uk.co.whisperingwind.vienna;
029:
030: import java.awt.event.KeyEvent;
031: import java.io.File;
032: import java.io.FileNotFoundException;
033: import java.sql.Connection;
034: import java.util.Collection;
035: import java.util.HashMap;
036: import java.util.Iterator;
037: import javax.swing.JFileChooser;
038: import javax.swing.JOptionPane;
039: import javax.swing.JPanel;
040: import javax.swing.JTextField;
041: import javax.swing.KeyStroke;
042: import javax.swing.text.Keymap;
043: import uk.co.whisperingwind.framework.Controller;
044: import uk.co.whisperingwind.framework.Dialogs;
045: import uk.co.whisperingwind.framework.ExceptionDialog;
046: import uk.co.whisperingwind.framework.ModelEvent;
047: import uk.co.whisperingwind.framework.SqlFileFilter;
048: import uk.co.whisperingwind.framework.ViewEvent;
049:
050: class MainController extends Controller {
051: private MainView mainView = null;
052: private int untitledCount = 1;
053: private ConfigModel configModel = null;
054: private ConnectionModel connectionModel = new ConnectionModel();
055: private JFileChooser fileChooser = new JFileChooser();
056: private String selectedConnection = null;
057:
058: private OpenList openList = new OpenList();
059:
060: /*
061: ** Gotta have one of these somewhere...
062: */
063:
064: public static void main(String args[]) {
065: ViennaSplash splash = new ViennaSplash();
066: boolean openedFile = false;
067: MainController controller = new MainController();
068:
069: for (int i = 0; i < args.length; i++) {
070: File theFile = new File(args[i]);
071:
072: if (theFile.exists()) {
073: controller.openTab(theFile);
074: openedFile = true;
075: }
076: }
077:
078: if (!openedFile)
079: controller.newUntitled();
080:
081: controller.pack();
082: splash.dispose();
083: }
084:
085: private MainController() {
086: try {
087: configModel = new ConfigModel();
088: configModel.addObserver(this );
089: mainView = new MainView(configModel);
090: mainView.addObserver(this );
091: connectionModel.addObserver(this );
092: enableGUI();
093:
094: fileChooser.addChoosableFileFilter(new SqlFileFilter());
095: } catch (FileNotFoundException ex) {
096: Dialogs
097: .showError("vienna.xml not found",
098: "Please install vienna.xml in your home directory.");
099: System.exit(0);
100: } catch (ConfigException ex) {
101: Dialogs.showError("Error in vienna.xml", ex.getMessage());
102: System.exit(0);
103: }
104: }
105:
106: private void pack() {
107: mainView.pack();
108: }
109:
110: /*
111: ** Some model event occurred. See what and act accordingly...
112: */
113:
114: public void modelEvent(ModelEvent event) {
115: Object initiator = event.getInitiator();
116:
117: if (initiator == connectionModel)
118: handleConnectionEvent(event);
119: else if (initiator == configModel)
120: handleConfigEvent(event);
121: }
122:
123: /*
124: ** Handle an event in the ConnectionModel.
125: */
126:
127: private void handleConnectionEvent(ModelEvent event) {
128: String action = event.getField();
129:
130: if (action.equals("connect")) {
131: if (connectionModel.isConnected()) {
132: openList.setStatus("Connected to "
133: + connectionModel.getUrl());
134: } else {
135: openList.setStatus("Connection failed");
136: mainView.setSelectedConnection(null);
137: }
138: }
139:
140: enableGUI();
141: }
142:
143: /*
144: ** Very messy: Remove the observer on the MainView while the
145: ** ConfigModel is being updated. That way, I won't get change
146: ** events from the connection combo box and close the current
147: ** connection. Remember where I was connected and re-select it
148: ** when the ConfigModel update is complete.
149: */
150:
151: private void handleConfigEvent(ModelEvent event) {
152: String field = event.getField();
153:
154: if (field.equals("update")) {
155: String value = (String) event.getValue();
156:
157: if (value.equals("begin")) {
158: selectedConnection = mainView.getSelectedConnection();
159: mainView.deleteObserver(this );
160: } else if (value.equals("end")) {
161: /*
162: ** If the original connection no longer exists,
163: ** disconnect.
164: */
165:
166: if (!mainView.setSelectedConnection(selectedConnection))
167: connectionModel.disconnect();
168:
169: selectedConnection = null;
170: mainView.addObserver(this );
171: }
172: }
173: }
174:
175: /*
176: ** An event from the main view...
177: */
178:
179: public void viewEvent(ViewEvent event) {
180: Object initiator = event.getInitiator();
181:
182: try {
183: if (initiator instanceof MainView)
184: handleMainEvent(event);
185: else if (initiator instanceof QueryController)
186: handleQueryEvent(event);
187: else if (initiator instanceof LoginView)
188: handleLoginEvent(event);
189: } catch (Exception ex) {
190: new ExceptionDialog(ex);
191: }
192:
193: enableGUI();
194: }
195:
196: /*
197: ** Handle an event in the main window.
198: */
199:
200: private void handleMainEvent(ViewEvent event) {
201: String action = event.getArg1();
202: String arg = (String) event.getArg2();
203:
204: if (action.equals("connect"))
205: connectDatabase(arg);
206: else if (action.equals("changeTab"))
207: changeTab();
208: else if (action.equals("new"))
209: newUntitled();
210: else if (action.equals("open"))
211: openFile();
212: else if (action.equals("save"))
213: saveFile();
214: else if (action.equals("saveAs"))
215: saveFileAs();
216: else if (action.equals("saveAll"))
217: saveFileAll();
218: else if (action.equals("close"))
219: closeFile();
220: else if (action.equals("closeAll"))
221: closeFileAll();
222: else if (action.equals("config"))
223: configure();
224: else if (action.equals("execute"))
225: execute();
226: else if (action.equals("cancel"))
227: cancel();
228: else if (action.equals("commit"))
229: connectionModel.commit();
230: else if (action.equals("rollback"))
231: connectionModel.rollback();
232: else if (action.equals("schema"))
233: selectSchema();
234: else if (action.equals("export"))
235: exportResult();
236: else if (action.equals("about"))
237: showAbout();
238: else if (action.equals("cut"))
239: editAction(action);
240: else if (action.equals("copy"))
241: editAction(action);
242: else if (action.equals("paste"))
243: editAction(action);
244: else if (action.equals("clear"))
245: editAction(action);
246: else if (action.equals("popup"))
247: enableGUI();
248: else if (action.equals("undo"))
249: undoEdit();
250: else if (action.equals("redo"))
251: redoEdit();
252: else if (action.equals("quit"))
253: if (confirmAll())
254: System.exit(0);
255: }
256:
257: /*
258: ** Handle an event in a QueryController.
259: */
260:
261: private void handleQueryEvent(ViewEvent event) {
262: String action = event.getArg1();
263:
264: if (action.equals("updated"))
265: connectionModel.setUpdated();
266: else if (action.equals("edited"))
267: setTitle();
268: }
269:
270: /*
271: ** Handle an event in a LoginView.
272: */
273:
274: private void handleLoginEvent(ViewEvent event) {
275: String action = event.getArg1();
276: LoginView loginView = (LoginView) event.getInitiator();
277:
278: if (action.equals("ok")) {
279: String driverClass = loginView.getDriverClass();
280: String url = loginView.getUrl();
281: String userName = loginView.getUserName();
282: String password = loginView.getPassword();
283:
284: loginView.closeDialog();
285:
286: openConnection(driverClass, url, userName, password);
287: } else if (action.equals("cancel")) {
288: loginView.closeDialog();
289: }
290: }
291:
292: /*
293: ** Connect to a database. If the config has saved passwords, use
294: ** the values from the selected configuration. If passwords are
295: ** not saved, pop up a LoginView to get the password.
296: */
297:
298: private void connectDatabase(String configName) {
299: if (configModel.getSavePasswords()) {
300: connectionModel.disconnect();
301: openList.setStatus("Disconnected");
302: openConnection(configName);
303: } else {
304: connectionModel.disconnect();
305: openList.setStatus("Disconnected");
306: String url = configModel.getConnectionUrl(configName);
307:
308: if (url != null) {
309: LoginView loginView = new LoginView(configModel,
310: configName);
311: loginView.addObserver(this );
312: }
313: }
314: }
315:
316: /*
317: ** Open a database connection using values from a named
318: ** connection.
319: */
320:
321: private void openConnection(String configName) {
322: String url = configModel.getConnectionUrl(configName);
323:
324: if (url != null) {
325: String userName = configModel
326: .getConnectionUserName(configName);
327: String password = configModel
328: .getConnectionPassword(configName);
329: String driverClass = configModel
330: .getConnectionDriverClass(configName);
331:
332: connectionModel.setDriverClass(driverClass);
333: connectionModel.setUrl(url);
334: connectionModel.setUserName(userName);
335: connectionModel.setPassword(password);
336:
337: openList.setStatus("Connecting to " + url);
338: connectionModel.connect();
339: }
340: }
341:
342: /*
343: ** Open a database connection using the given values.
344: */
345:
346: private void openConnection(String driverClass, String url,
347: String userName, String password) {
348: connectionModel.setDriverClass(driverClass);
349: connectionModel.setUrl(url);
350: connectionModel.setUserName(userName);
351: connectionModel.setPassword(password);
352:
353: openList.setStatus("Connecting to " + url);
354: connectionModel.connect();
355: }
356:
357: /*
358: ** The selected tab in the main view's tabset has changed.
359: */
360:
361: private void changeTab() {
362: setTitle();
363: QueryController controller = openList.getActiveController();
364:
365: if (controller != null)
366: controller.focusText();
367:
368: enableGUI();
369: }
370:
371: /*
372: ** New tab with an "untitled" title.
373: */
374:
375: private void newUntitled() {
376: Integer sequence = new Integer(untitledCount++);
377: String title = "Untitled-" + sequence.toString();
378: newTitled(title);
379: }
380:
381: /*
382: ** New tab with a title.
383: */
384:
385: private void newTitled(String title) {
386: QueryController queryController = new QueryController(
387: configModel);
388: queryController.addObserver(this );
389: openList.addController(queryController);
390: mainView.addTab(title, (JPanel) queryController.getWidget());
391: setTitle();
392: }
393:
394: /*
395: ** Execute the query in the currently selected tab.
396: */
397:
398: private void execute() {
399: QueryController controller = openList.getActiveController();
400: Connection connection = connectionModel.getConnection();
401: controller.execute(connection);
402: }
403:
404: /*
405: ** Cancel an executing query.
406: */
407:
408: private void cancel() {
409: QueryController controller = openList.getActiveController();
410: controller.cancelExecute();
411: }
412:
413: /*
414: ** Open a schema list controller.
415: */
416:
417: private void selectSchema() {
418: Connection connection = connectionModel.getConnection();
419:
420: if (connection != null) {
421: SchemaListController schemaListController = SchemaListController
422: .singleton(connection, configModel);
423: }
424: }
425:
426: /*
427: ** Export query results to a file.
428: */
429:
430: private void exportResult() {
431: QueryController controller = openList.getActiveController();
432: controller.exportResult();
433: }
434:
435: /*
436: ** Show the about dialog.
437: */
438:
439: private void showAbout() {
440: new AboutDialog(mainView.getContent());
441: }
442:
443: /*
444: **
445: */
446:
447: private void editAction(String action) {
448: QueryController controller = openList.getActiveController();
449: controller.editAction(action);
450: }
451:
452: /*
453: ** Open a file in a new tab.
454: */
455:
456: private void openFile() {
457: fileChooser.rescanCurrentDirectory();
458: int result = fileChooser.showOpenDialog(mainView.getContent());
459:
460: if (result == JFileChooser.APPROVE_OPTION) {
461: File theFile = fileChooser.getSelectedFile();
462:
463: if (theFile != null) {
464: String path = theFile.getAbsolutePath();
465:
466: if (openList.containsFile(path)) {
467: Dialogs.showInformation("File already open",
468: theFile.getName() + " is already open");
469: } else {
470: openTab(theFile);
471: }
472: }
473: }
474: }
475:
476: /*
477: ** Add a new tab and load the contents of the named file.
478: */
479:
480: private void openTab(File theFile) {
481: if (theFile.isFile()) {
482: if (theFile.canRead()) {
483: newTitled(theFile.getName());
484: QueryController controller = openList
485: .getActiveController();
486: controller.openFile(theFile.getAbsolutePath());
487: setTitle();
488: openList.addFile(theFile.getPath());
489: }
490: }
491: }
492:
493: /*
494: ** Save the file in the selected tab.
495: */
496:
497: private boolean saveFile() {
498: boolean saved = false;
499: QueryController controller = openList.getActiveController();
500:
501: if (controller.hasFileName())
502: saved = controller.saveFile();
503: else
504: saved = saveFileAs();
505:
506: setTitle();
507:
508: return saved;
509: }
510:
511: /*
512: ** Save the file in the selected tab with a new name.
513: */
514:
515: private boolean saveFileAs() {
516: boolean saved = false;
517: fileChooser.setSelectedFile(new File(openList.getActivePath()));
518: int result = fileChooser.showSaveDialog(mainView.getContent());
519:
520: if (result == JFileChooser.APPROVE_OPTION) {
521: File theFile = fileChooser.getSelectedFile();
522:
523: if (theFile != null) {
524: QueryController controller = openList
525: .getActiveController();
526: String oldName = controller.getFileName();
527: saved = controller
528: .saveFileAs(theFile.getAbsolutePath());
529:
530: if (saved) {
531: mainView.setSelectedTitle(theFile.getName());
532: openList.renameFile(oldName, controller
533: .getFileName());
534: setTitle();
535: }
536: }
537: }
538:
539: setTitle();
540:
541: return saved;
542: }
543:
544: /*
545: ** Save all open queries.
546: */
547:
548: private boolean saveFileAll() {
549: boolean saved = true;
550:
551: for (int i = 0; i < mainView.getQueryCount() && saved; i++) {
552: mainView.selectQuery(i);
553: saved = saveFile();
554: }
555:
556: return saved;
557: }
558:
559: /*
560: ** Close the active query.
561: */
562:
563: private void closeFile() {
564: boolean canClose = true;
565: QueryController controller = openList.getActiveController();
566:
567: if (controller.isDirty())
568: canClose = confirmClose(mainView.getSelectedTitle());
569:
570: if (canClose) {
571: controller.cancelExecute();
572: openList.removeFile(controller.getFileName());
573: controller.cleanUp();
574: controller.deleteObserver(this );
575: setTitle();
576: }
577: }
578:
579: /*
580: ** Close all queries.
581: */
582:
583: private void closeFileAll() {
584: if (confirmAll()) {
585: QueryController controller = openList.getActiveController();
586:
587: while (controller != null) {
588: controller.cancelExecute();
589: controller.cleanUp();
590: controller.deleteObserver(this );
591: openList.removeFile(controller.getFileName());
592: controller = openList.getActiveController();
593: }
594:
595: setTitle();
596: }
597: }
598:
599: private void configure() {
600: ConfigController configController = ConfigController.singleton(
601: mainView.getContent(), configModel);
602: }
603:
604: /*
605: ** A query has been modified. Make sure the user really wants to
606: ** close it. Returns true if the file can be closed.
607: */
608:
609: private boolean confirmClose(String title) {
610: boolean confirmed = false;
611: int reply = Dialogs.getConfirm("File modified", title
612: + " has been modified. Save before closing ?");
613:
614: switch (reply) {
615: case JOptionPane.YES_OPTION:
616: confirmed = saveFile();
617: break;
618:
619: case JOptionPane.NO_OPTION:
620: confirmed = true;
621: break;
622:
623: case JOptionPane.CANCEL_OPTION:
624: break;
625:
626: default:
627: break;
628: }
629:
630: return confirmed;
631: }
632:
633: /*
634: ** Confirm all files can be closed. Asks the user to confirm each
635: ** file that has changed.
636: */
637:
638: private boolean confirmAll() {
639: boolean canClose = true;
640:
641: if (connectionModel.isUpdated())
642: canClose = confirmDisconnect();
643:
644: for (int i = 0; i < mainView.getQueryCount() && canClose; i++) {
645: mainView.selectQuery(i);
646: QueryController controller = openList.getActiveController();
647:
648: if (controller.isDirty())
649: canClose = confirmClose(mainView.getSelectedTitle());
650: }
651:
652: return canClose;
653: }
654:
655: /*
656: ** If there are uncommited changes, see if these should be
657: ** committed or rolled back.
658: */
659:
660: private boolean confirmDisconnect() {
661: boolean confirmed = true;
662: Object[] options = { "Commit", "Rollback", "Cancel" };
663: int reply = Dialogs.getOption("Warning",
664: "Connection has uncommited changes", options,
665: options[0]);
666:
667: if (reply == 0)
668: connectionModel.commit();
669: else if (reply == 1)
670: connectionModel.rollback();
671: else if (reply == 2)
672: confirmed = false;
673:
674: return confirmed;
675: }
676:
677: /*
678: ** Set the main window title.
679: */
680:
681: private void setTitle() {
682: String title = "";
683: QueryController controller = openList.getActiveController();
684:
685: if (controller != null) {
686: title = controller.getFileName();
687:
688: if (title.length() == 0)
689: title = mainView.getSelectedTitle();
690:
691: if (controller.isDirty())
692: title = title + " [Edited]";
693: }
694:
695: mainView.setFileTitle(title);
696: }
697:
698: /**
699: ** Undo the last edit.
700: */
701:
702: private void undoEdit() {
703: QueryController controller = openList.getActiveController();
704: controller.undoEdit();
705: }
706:
707: /**
708: ** Redo the last edit.
709: */
710:
711: private void redoEdit() {
712: QueryController controller = openList.getActiveController();
713: controller.redoEdit();
714: }
715:
716: /**
717: ** Enable or disable some menu items and buttons depending on
718: ** whether I am connected and whether there are any open queries.
719: */
720:
721: private void enableGUI() {
722: QueryController controller = openList.getActiveController();
723: boolean connected = connectionModel.isConnected();
724: boolean updated = connectionModel.isUpdated();
725: boolean haveFile = false;
726: boolean executing = false;
727: boolean haveSelection = false;
728: boolean haveClip = true; // FIXME: How to find out?
729: boolean canExport = false;
730: boolean canUndo = false;
731: boolean canRedo = false;
732:
733: if (controller != null) {
734: haveFile = true;
735: executing = controller.isExecuting();
736: haveSelection = controller.hasSelection();
737: canExport = controller.canExport();
738: canUndo = controller.canUndo();
739: canRedo = controller.canRedo();
740: }
741:
742: mainView.enableGUI(connected, haveFile, executing, updated,
743: haveSelection, haveClip, canExport, canUndo, canRedo);
744: }
745:
746: /*
747: ** MainController has one of these to keep track of which files I
748: ** have open and which QueryController is handling which pane.
749: */
750:
751: private class OpenList {
752: private HashMap controllerMap = new HashMap();
753: private HashMap pathMap = new HashMap();
754:
755: public void addController(QueryController controller) {
756: controllerMap.put(controller.getWidget(), controller);
757: }
758:
759: public void addFile(String path) {
760: pathMap.put(path, null);
761: }
762:
763: public void removeFile(String path) {
764: removeActiveController();
765: pathMap.remove(path);
766: }
767:
768: public void renameFile(String oldPath, String newPath) {
769: pathMap.remove(oldPath);
770: addFile(newPath);
771: }
772:
773: /*
774: ** Returns the QueryController for the currently active query.
775: */
776:
777: public QueryController getActiveController() {
778: Object active = mainView.getSelected();
779: return getController(active);
780: }
781:
782: /*
783: ** Returns the controller for the specified query pane.
784: */
785:
786: public QueryController getController(Object selected) {
787: return (QueryController) controllerMap.get(selected);
788: }
789:
790: /*
791: ** Returns the path to the file in the active pane.
792: */
793:
794: public String getActivePath() {
795: String path = "";
796: QueryController controller = getActiveController();
797:
798: if (controller != null) {
799: path = controller.getFileName();
800:
801: if (path.length() == 0)
802: path = mainView.getSelectedTitle();
803: }
804:
805: return path;
806: }
807:
808: private void removeActiveController() {
809: Object removed = mainView.removeSelected();
810: controllerMap.remove(removed);
811: }
812:
813: /*
814: ** Returns true if the specified file is open.
815: */
816:
817: public boolean containsFile(String path) {
818: return pathMap.containsKey(path);
819: }
820:
821: public void setStatus(String status) {
822: Collection controllers = controllerMap.values();
823: Iterator i = controllers.iterator();
824:
825: while (i.hasNext()) {
826: QueryController controller = (QueryController) i.next();
827: controller.setStatus(status);
828: }
829: }
830: }
831:
832: /*
833: ** Cute hack to make JTextField pass on <Enter> key events. Means
834: ** the default button will be activated when a JTextField has
835: ** focus in a dialog. Blagged from Java Developer Connection.
836: */
837:
838: static {
839: JTextField textField = new JTextField();
840: KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
841: Keymap map = textField.getKeymap();
842: map.removeKeyStrokeBinding(enter);
843: }
844: }
|