2 * jSite2 - SwingInterface.java -
3 * Copyright © 2008 David Roden
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 package net.pterodactylus.jsite.gui;
22 import java.awt.event.ActionEvent;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Locale;
33 import java.util.Properties;
34 import java.util.concurrent.Executor;
35 import java.util.concurrent.Executors;
36 import java.util.logging.Level;
37 import java.util.logging.LogRecord;
38 import java.util.logging.Logger;
40 import javax.swing.AbstractAction;
41 import javax.swing.Action;
42 import javax.swing.JOptionPane;
43 import javax.swing.UIManager;
44 import javax.swing.UnsupportedLookAndFeelException;
46 import net.pterodactylus.jsite.core.Core;
47 import net.pterodactylus.jsite.core.CoreListener;
48 import net.pterodactylus.jsite.core.Node;
49 import net.pterodactylus.jsite.core.Project;
50 import net.pterodactylus.jsite.i18n.I18n;
51 import net.pterodactylus.jsite.i18n.gui.I18nAction;
52 import net.pterodactylus.util.io.Closer;
53 import net.pterodactylus.util.logging.Logging;
54 import net.pterodactylus.util.logging.LoggingListener;
57 * The Swing user interface.
59 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
62 public class SwingInterface implements CoreListener, LoggingListener {
65 private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
67 /** The application core. */
68 private final Core core;
70 /** The configuration directory. */
71 private final String configDirectory;
73 /** The main window. */
74 private MainWindow mainWindow;
77 private Executor threadPool = Executors.newCachedThreadPool();
79 /** The logger window. */
80 private LogWindow logWindow;
82 /** The “configure” action. */
83 private I18nAction configureAction;
85 /** The “import config” action. */
86 private I18nAction importConfigAction;
88 /** The “quit” action. */
89 private I18nAction quitAction;
91 /** The “manage nodes” action. */
92 private I18nAction manageNodesAction;
94 /** The “connect to node” (simple mode) action. */
95 private I18nAction nodeConnectAction;
97 /** The “disconnect from node” (simple mode) action. */
98 private I18nAction nodeDisconnectAction;
100 /** All node menu items. */
101 private List<Action> nodeConnectActions = Collections.synchronizedList(new ArrayList<Action>());
103 /** Mapping from nodes to node connect actions. */
104 private Map<Node, Action> nodeNodeConnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
106 /** Mapping from node connect actions to nodes. */
107 private Map<Action, Node> nodeConnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
109 /** All node disconnect actions. */
110 private List<Action> nodeDisconnectActions = Collections.synchronizedList(new ArrayList<Action>());
112 /** Mapping from nodes to node disconnect actions. */
113 private Map<Node, Action> nodeNodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
115 /** Mapping from node disconnect actions to nodes. */
116 private Map<Action, Node> nodeDisconnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
118 /** The node manager dialog. */
119 private ManageNodesDialog manageNodesDialog;
121 /** All lanugage menu items. */
122 private List<I18nAction> languageActions = new ArrayList<I18nAction>();
124 /** The “about” action. */
125 private I18nAction helpAboutAction;
127 /** The “add project” action. */
128 private I18nAction addProjectAction;
130 /** The “clone project” action. */
131 private I18nAction cloneProjectAction;
133 /** The “delete project” action. */
134 private I18nAction deleteProjectAction;
136 /** The “about” dialog. */
137 private AboutDialog aboutDialog;
139 /** The configuration dialog. */
140 private ConfigurationDialog configurationDialog;
142 /** The list of all defined nodes. */
143 private List<Node> nodeList;
149 /** The advanced mode. */
150 private boolean advancedMode;
152 /** Whether to antialias the GUI. */
153 private boolean antialias;
155 /** The control font. */
156 private String controlFont;
158 /** The user font. */
159 private String userFont;
161 /** The class name of the look and feel. */
162 private String lookAndFeel;
165 * Creates a new swing interface.
168 * The core to operate on
169 * @param configDirectory
170 * The directory the configuration is stored in
172 public SwingInterface(Core core, String configDirectory) {
174 this.configDirectory = configDirectory;
175 I18n.setLocale(Locale.ENGLISH);
177 if (lookAndFeel != null) {
179 UIManager.setLookAndFeel(lookAndFeel);
180 } catch (ClassNotFoundException cnfe1) {
181 logger.log(Level.WARNING, "could not load look and feel", cnfe1);
182 } catch (InstantiationException ie1) {
183 logger.log(Level.WARNING, "could not load look and feel", ie1);
184 } catch (IllegalAccessException iae1) {
185 logger.log(Level.WARNING, "could not load look and feel", iae1);
186 } catch (UnsupportedLookAndFeelException ulafe1) {
187 logger.log(Level.WARNING, "could not load look and feel", ulafe1);
191 System.setProperty("swing.aatext", "true");
193 if (controlFont != null) {
194 System.setProperty("swing.plaf.metal.controlFont", controlFont);
196 if (userFont != null) {
197 System.setProperty("swing.plaf.metal.userFont", userFont);
201 mainWindow = new MainWindow(this);
202 mainWindow.setAdvancedMode(advancedMode);
203 logWindow = new LogWindow();
211 * Returns the core that is controlled by the Swing interface.
220 * Returns the main window of the Swing interface.
222 * @return The main window
224 MainWindow getMainWindow() {
229 * Returns whether the advanced mode is activated.
231 * @return <code>true</code> if the advanced mode is activated,
232 * <code>false</code> if the simple mode is activated
234 boolean isAdvancedMode() {
239 * Returns the “configure” action.
241 * @return The “configure” action
243 I18nAction getConfigureAction() {
244 return configureAction;
248 * Returns the “import config” action.
250 * @return The “import config” action
252 I18nAction getImportConfigAction() {
253 return importConfigAction;
257 * Returns the “quit” action.
259 * @return The “quit” action
261 I18nAction getQuitAction() {
266 * Returns the “manage nodes” action.
268 * @return The “manage nodes” action
270 I18nAction getManageNodesAction() {
271 return manageNodesAction;
275 * Returns the “connect to node” action.
277 * @return The “connect to node” action
279 I18nAction getNodeConnectAction() {
280 return nodeConnectAction;
284 * Returns all “connect node” actions.
286 * @return All “connect node” actions
288 List<Action> getNodeConnectActions() {
289 return nodeConnectActions;
293 * Returns the “disconnect from node” action.
295 * @return The “disconnect from node” action
297 I18nAction getNodeDisconnectAction() {
298 return nodeDisconnectAction;
302 * Returns all “disconnect node” actions.
304 * @return All “disconnect node” action
306 List<Action> getNodeDisconnectActions() {
307 return nodeDisconnectActions;
311 * Returns all language actions.
313 * @return All language actions
315 List<I18nAction> getLanguageActions() {
316 return languageActions;
320 * Returns the “about” action.
322 * @return The “about” action
324 I18nAction getHelpAboutAction() {
325 return helpAboutAction;
329 * Returns the “add project” action.
331 * @return The “add project” action
333 I18nAction getAddProjectAction() {
334 return addProjectAction;
338 * Returns the “clone project” action.
340 * @return The “clone project” action
342 I18nAction getCloneProjectAction() {
343 return cloneProjectAction;
347 * Returns the “delete project” action.
349 * @return The “delete project” action
351 I18nAction getDeleteProjectAction() {
352 return deleteProjectAction;
368 * Loads the configuration of the interface.
370 private void loadConfig() {
371 /* initialize default stuff. */
373 /* now read config. */
374 File configFile = new File(configDirectory, "swing-interface.properties");
375 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
376 System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
379 Properties configProperties = new Properties();
380 FileInputStream configInputStream = null;
382 configInputStream = new FileInputStream(configFile);
383 configProperties.load(configInputStream);
384 } catch (IOException ioe1) {
385 System.err.println("could not load config, " + ioe1.getMessage());
387 Closer.close(configInputStream);
389 if (configProperties.containsKey("advancedMode")) {
390 advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
392 if (configProperties.containsKey("antialias")) {
393 antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
395 if (configProperties.containsKey("controlFont")) {
396 controlFont = configProperties.getProperty("controlFont");
398 if (configProperties.containsKey("userFont")) {
399 userFont = configProperties.getProperty("userFont");
401 if (configProperties.containsKey("lookAndFeel")) {
402 lookAndFeel = configProperties.getProperty("lookAndFeel");
404 if (configProperties.containsKey("language")) {
405 I18n.setLocale(new Locale(configProperties.getProperty("language")));
410 * Saves the configuration.
412 private void saveConfig() {
413 File configDirectory = new File(this.configDirectory);
414 if (!configDirectory.exists()) {
415 if (!configDirectory.mkdirs()) {
416 System.err.println("could not create “" + this.configDirectory + "”!");
420 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
421 System.err.println("can not access “" + this.configDirectory + "”!");
424 File configFile = new File(configDirectory, "swing-interface.properties");
425 Properties configProperties = new Properties();
426 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
427 configProperties.setProperty("antialias", String.valueOf(antialias));
428 if (controlFont != null) {
429 configProperties.setProperty("controlFont", controlFont);
431 if (userFont != null) {
432 configProperties.setProperty("userFont", userFont);
434 if (lookAndFeel != null) {
435 configProperties.setProperty("lookAndFeel", lookAndFeel);
437 configProperties.setProperty("language", I18n.getLocale().getLanguage());
438 FileOutputStream configOutputStream = null;
440 configOutputStream = new FileOutputStream(configFile);
441 configProperties.store(configOutputStream, "configuration of swing interface");
442 } catch (IOException ioe1) {
443 System.err.println("could not save config, " + ioe1.getMessage());
445 Closer.close(configOutputStream);
450 * Initializes all actions.
452 private void initActions() {
453 configureAction = new I18nAction("mainWindow.menu.jSite.configure") {
458 @SuppressWarnings("synthetic-access")
459 public void actionPerformed(ActionEvent actionEvent) {
463 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
468 @SuppressWarnings("synthetic-access")
469 public void actionPerformed(ActionEvent actionEvent) {
473 quitAction = new I18nAction("mainWindow.menu.jSite.quit") {
478 @SuppressWarnings("synthetic-access")
479 public void actionPerformed(ActionEvent actionEvent) {
483 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
488 @SuppressWarnings("synthetic-access")
489 public void actionPerformed(ActionEvent actionEvent) {
493 nodeConnectAction = new I18nAction("mainWindow.menu.node.item.connect", false) {
495 @SuppressWarnings("synthetic-access")
496 public void actionPerformed(ActionEvent actionEvent) {
497 List<Node> nodes = core.getNodes();
498 if (nodes.isEmpty()) {
501 nodeConnect(nodes.get(0));
505 nodeDisconnectAction = new I18nAction("mainWindow.menu.node.item.disconnect", false) {
510 @SuppressWarnings("synthetic-access")
511 public void actionPerformed(ActionEvent e) {
512 List<Node> nodes = core.getNodes();
513 if (nodes.isEmpty()) {
516 nodeDisconnect(nodes.get(0));
519 rebuildNodeActions(core.getNodes());
520 List<Locale> availableLanguages = I18n.findAvailableLanguages();
521 for (final Locale locale: availableLanguages) {
522 I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
524 @SuppressWarnings("synthetic-access")
525 public void actionPerformed(ActionEvent e) {
526 changeLanguage(locale, this);
530 if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
531 languageAction.setEnabled(false);
533 languageActions.add(languageAction);
535 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
540 @SuppressWarnings("synthetic-access")
541 public void actionPerformed(ActionEvent actionEvent) {
545 addProjectAction = new I18nAction("mainWindow.button.addProject") {
550 @SuppressWarnings("synthetic-access")
551 public void actionPerformed(ActionEvent actionEvent) {
555 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
560 @SuppressWarnings("synthetic-access")
561 public void actionPerformed(ActionEvent actionEvent) {
565 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
570 @SuppressWarnings("synthetic-access")
571 public void actionPerformed(ActionEvent actionEvent) {
578 * Initializes all child dialogs.
580 private void initDialogs() {
581 manageNodesDialog = new ManageNodesDialog(this);
582 aboutDialog = new AboutDialog(this);
583 configurationDialog = new ConfigurationDialog(this);
591 * Shows the configuration dialog.
593 private void configure() {
594 configurationDialog.setAdvancedMode(advancedMode);
595 configurationDialog.setAntialias(antialias);
596 configurationDialog.setControlFont(controlFont);
597 configurationDialog.setUserFont(userFont);
598 configurationDialog.setLookAndFeel(lookAndFeel);
599 configurationDialog.setVisible(true);
600 if (!configurationDialog.wasCancelled()) {
601 advancedMode = configurationDialog.isAdvancedMode();
602 if (!advancedMode && (nodeList.size() > 1)) {
603 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
605 mainWindow.setAdvancedMode(advancedMode);
606 antialias = configurationDialog.isAntialias();
607 controlFont = configurationDialog.getControlFont();
608 userFont = configurationDialog.getUserFont();
609 lookAndFeel = configurationDialog.getLookAndFeel();
615 * Imports old jSite configuration.
617 private void importConfig() {
624 private void quit() {
630 * Rebuilds all node connect and disconnect actions.
635 private void rebuildNodeActions(List<Node> nodes) {
636 nodeConnectActions.clear();
637 nodeNodeConnectActions.clear();
638 nodeConnectActionNodes.clear();
639 nodeDisconnectActions.clear();
640 nodeNodeDisconnectActions.clear();
641 nodeDisconnectActionNodes.clear();
642 for (Node node: nodes) {
643 Action nodeConnectAction = new AbstractAction(node.getName()) {
648 @SuppressWarnings("synthetic-access")
649 public void actionPerformed(ActionEvent e) {
650 Node node = nodeConnectActionNodes.get(this);
654 nodeConnectActions.add(nodeConnectAction);
655 nodeConnectActionNodes.put(nodeConnectAction, node);
656 nodeNodeConnectActions.put(node, nodeConnectAction);
657 Action nodeDisconnectAction = new AbstractAction(node.getName()) {
662 @SuppressWarnings("synthetic-access")
663 public void actionPerformed(ActionEvent e) {
664 Node node = nodeDisconnectActionNodes.get(this);
665 nodeDisconnect(node);
668 // nodeDisconnectActions.add(nodeDisconnectAction);
669 nodeDisconnectActionNodes.put(nodeDisconnectAction, node);
670 nodeNodeDisconnectActions.put(node, nodeDisconnectAction);
675 * Pops up the “manage nodes” dialog.
677 private void manageNodes() {
679 manageNodesDialog.setNodeList(nodeList);
680 manageNodesDialog.setVisible(true);
681 nodeList = manageNodesDialog.getNodeList();
682 rebuildNodeActions(nodeList);
683 mainWindow.refreshNodeMenuItems();
685 if (nodeList.isEmpty()) {
686 Node newNode = new Node();
687 newNode.setName(I18n.get("general.defaultNode.name"));
688 newNode.setHostname("localhost");
689 newNode.setPort(9481);
690 nodeList.add(newNode);
692 Node firstNode = nodeList.get(0);
693 EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
694 editNodeDialog.setNodeName(firstNode.getName());
695 editNodeDialog.setNodeHostname(firstNode.getHostname());
696 editNodeDialog.setNodePort(firstNode.getPort());
697 editNodeDialog.setVisible(true);
698 if (!editNodeDialog.wasCancelled()) {
699 firstNode.setName(editNodeDialog.getNodeName());
700 firstNode.setHostname(editNodeDialog.getNodeHostname());
701 firstNode.setPort(editNodeDialog.getNodePort());
702 /* TODO - give to core. */
708 * Connects to the node.
711 * The node to connect to
713 private void nodeConnect(final Node node) {
714 threadPool.execute(new Runnable() {
719 @SuppressWarnings("synthetic-access")
721 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
722 core.connectToNode(node);
728 * Disconnects from the node.
731 * The node to disconnect from
733 private void nodeDisconnect(Node node) {
734 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
735 core.disconnectFromNode(node);
739 * Changes the language of the interface. This method also disables the
740 * action for the newly set language and enables all others.
744 * @param languageAction
745 * The action that triggered the change
747 private void changeLanguage(Locale newLocale, I18nAction languageAction) {
748 for (I18nAction i18nAction: languageActions) {
749 i18nAction.setEnabled(i18nAction != languageAction);
751 I18n.setLocale(newLocale);
755 * Shows the “about” dialog.
757 private void helpAbout() {
758 aboutDialog.setVisible(true);
764 private void addProject() {
765 Project project = new Project();
766 project.setName("New Project");
767 project.setDescription("");
773 private void cloneProject() {
780 private void deleteProject() {
785 // INTERFACE CoreListener
791 public void loadingProjectsDone(String directory) {
792 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
798 public void loadingProjectsFailed(String directory, Throwable throwable) {
799 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
805 public void savingProjectsDone(String directory) {
806 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
812 public void savingProjectsFailed(String directory, Throwable throwabled) {
819 public void loadingNodesDone(String directory) {
820 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
826 public void loadingNodesFailed(String directory, Throwable throwable) {
833 public void savingNodesDone(String directory) {
834 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
840 public void savingNodesFailed(String directory, Throwable throwable) {
847 public void coreLoaded() {
848 this.nodeList = core.getNodes();
849 manageNodesDialog.setNodeList(nodeList);
850 mainWindow.setVisible(true);
851 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
857 public void coreStopped() {
858 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
864 public void nodeConnecting(Node node) {
865 Action nodeConnectAction = nodeNodeConnectActions.get(node);
866 nodeConnectActions.remove(nodeConnectAction);
867 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
868 mainWindow.refreshNodeMenuItems();
874 public void nodeConnected(Node node) {
875 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
876 nodeDisconnectActions.add(nodeDisconnectAction);
877 mainWindow.refreshNodeMenuItems();
883 public void nodeDisconnected(Node node, Throwable throwable) {
884 Action nodeConnectAction = nodeNodeConnectActions.get(node);
885 nodeConnectActions.add(nodeConnectAction);
886 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
887 nodeDisconnectActions.remove(nodeDisconnectAction);
888 mainWindow.refreshNodeMenuItems();
892 // INTERFACE LoggingListener
898 public void logged(LogRecord logRecord) {
899 logWindow.logged(logRecord);