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.image.IconLoader;
53 import net.pterodactylus.util.io.Closer;
54 import net.pterodactylus.util.logging.Logging;
55 import net.pterodactylus.util.logging.LoggingListener;
58 * The Swing user interface.
60 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
63 public class SwingInterface implements CoreListener, LoggingListener {
66 private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
68 /** The application core. */
69 private final Core core;
71 /** The configuration directory. */
72 private final String configDirectory;
74 /** The main window. */
75 private MainWindow mainWindow;
78 private Executor threadPool = Executors.newCachedThreadPool();
80 /** The logger window. */
81 private LogWindow logWindow;
83 /** The “configure” action. */
84 private I18nAction configureAction;
86 /** The “import config” action. */
87 private I18nAction importConfigAction;
89 /** The “quit” action. */
90 private I18nAction quitAction;
92 /** The “manage nodes” action. */
93 private I18nAction manageNodesAction;
95 /** The “connect to node” (simple mode) action. */
96 private I18nAction nodeConnectAction;
98 /** The “disconnect from node” (simple mode) action. */
99 private I18nAction nodeDisconnectAction;
101 /** All node menu items. */
102 private List<Action> nodeConnectActions = Collections.synchronizedList(new ArrayList<Action>());
104 /** Mapping from nodes to node connect actions. */
105 private Map<Node, Action> nodeNodeConnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
107 /** Mapping from node connect actions to nodes. */
108 private Map<Action, Node> nodeConnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
110 /** All node disconnect actions. */
111 private List<Action> nodeDisconnectActions = Collections.synchronizedList(new ArrayList<Action>());
113 /** Mapping from nodes to node disconnect actions. */
114 private Map<Node, Action> nodeNodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
116 /** Mapping from node disconnect actions to nodes. */
117 private Map<Action, Node> nodeDisconnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
119 /** The node manager dialog. */
120 private ManageNodesDialog manageNodesDialog;
122 /** All lanugage menu items. */
123 private List<I18nAction> languageActions = new ArrayList<I18nAction>();
125 /** The “about” action. */
126 private I18nAction helpAboutAction;
128 /** The “add project” action. */
129 private I18nAction addProjectAction;
131 /** The “clone project” action. */
132 private I18nAction cloneProjectAction;
134 /** The “delete project” action. */
135 private I18nAction deleteProjectAction;
137 /** The “about” dialog. */
138 private AboutDialog aboutDialog;
140 /** The configuration dialog. */
141 private ConfigurationDialog configurationDialog;
143 /** The list of all defined nodes. */
144 private List<Node> nodeList;
150 /** The advanced mode. */
151 private boolean advancedMode;
153 /** Whether to antialias the GUI. */
154 private boolean antialias;
156 /** The control font. */
157 private String controlFont;
159 /** The user font. */
160 private String userFont;
162 /** The class name of the look and feel. */
163 private String lookAndFeel;
166 * Creates a new swing interface.
169 * The core to operate on
170 * @param configDirectory
171 * The directory the configuration is stored in
173 public SwingInterface(Core core, String configDirectory) {
175 this.configDirectory = configDirectory;
176 I18n.setLocale(Locale.ENGLISH);
178 if (lookAndFeel != null) {
180 UIManager.setLookAndFeel(lookAndFeel);
181 } catch (ClassNotFoundException cnfe1) {
182 logger.log(Level.WARNING, "could not load look and feel", cnfe1);
183 } catch (InstantiationException ie1) {
184 logger.log(Level.WARNING, "could not load look and feel", ie1);
185 } catch (IllegalAccessException iae1) {
186 logger.log(Level.WARNING, "could not load look and feel", iae1);
187 } catch (UnsupportedLookAndFeelException ulafe1) {
188 logger.log(Level.WARNING, "could not load look and feel", ulafe1);
192 System.setProperty("swing.aatext", "true");
194 if (controlFont != null) {
195 System.setProperty("swing.plaf.metal.controlFont", controlFont);
197 if (userFont != null) {
198 System.setProperty("swing.plaf.metal.userFont", userFont);
202 mainWindow = new MainWindow(this);
203 mainWindow.setAdvancedMode(advancedMode);
204 logWindow = new LogWindow();
212 * Returns the core that is controlled by the Swing interface.
221 * Returns the main window of the Swing interface.
223 * @return The main window
225 MainWindow getMainWindow() {
230 * Returns whether the advanced mode is activated.
232 * @return <code>true</code> if the advanced mode is activated,
233 * <code>false</code> if the simple mode is activated
235 boolean isAdvancedMode() {
240 * Returns the “configure” action.
242 * @return The “configure” action
244 I18nAction getConfigureAction() {
245 return configureAction;
249 * Returns the “import config” action.
251 * @return The “import config” action
253 I18nAction getImportConfigAction() {
254 return importConfigAction;
258 * Returns the “quit” action.
260 * @return The “quit” action
262 I18nAction getQuitAction() {
267 * Returns the “manage nodes” action.
269 * @return The “manage nodes” action
271 I18nAction getManageNodesAction() {
272 return manageNodesAction;
276 * Returns the “connect to node” action.
278 * @return The “connect to node” action
280 I18nAction getNodeConnectAction() {
281 return nodeConnectAction;
285 * Returns all “connect node” actions.
287 * @return All “connect node” actions
289 List<Action> getNodeConnectActions() {
290 return nodeConnectActions;
294 * Returns the “disconnect from node” action.
296 * @return The “disconnect from node” action
298 I18nAction getNodeDisconnectAction() {
299 return nodeDisconnectAction;
303 * Returns all “disconnect node” actions.
305 * @return All “disconnect node” action
307 List<Action> getNodeDisconnectActions() {
308 return nodeDisconnectActions;
312 * Returns all language actions.
314 * @return All language actions
316 List<I18nAction> getLanguageActions() {
317 return languageActions;
321 * Returns the “about” action.
323 * @return The “about” action
325 I18nAction getHelpAboutAction() {
326 return helpAboutAction;
330 * Returns the “add project” action.
332 * @return The “add project” action
334 I18nAction getAddProjectAction() {
335 return addProjectAction;
339 * Returns the “clone project” action.
341 * @return The “clone project” action
343 I18nAction getCloneProjectAction() {
344 return cloneProjectAction;
348 * Returns the “delete project” action.
350 * @return The “delete project” action
352 I18nAction getDeleteProjectAction() {
353 return deleteProjectAction;
369 * Loads the configuration of the interface.
371 private void loadConfig() {
372 /* initialize default stuff. */
374 /* now read config. */
375 File configFile = new File(configDirectory, "swing-interface.properties");
376 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
377 System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
380 Properties configProperties = new Properties();
381 FileInputStream configInputStream = null;
383 configInputStream = new FileInputStream(configFile);
384 configProperties.load(configInputStream);
385 } catch (IOException ioe1) {
386 System.err.println("could not load config, " + ioe1.getMessage());
388 Closer.close(configInputStream);
390 if (configProperties.containsKey("advancedMode")) {
391 advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
393 if (configProperties.containsKey("antialias")) {
394 antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
396 if (configProperties.containsKey("controlFont")) {
397 controlFont = configProperties.getProperty("controlFont");
399 if (configProperties.containsKey("userFont")) {
400 userFont = configProperties.getProperty("userFont");
402 if (configProperties.containsKey("lookAndFeel")) {
403 lookAndFeel = configProperties.getProperty("lookAndFeel");
405 if (configProperties.containsKey("language")) {
406 I18n.setLocale(new Locale(configProperties.getProperty("language")));
411 * Saves the configuration.
413 private void saveConfig() {
414 File configDirectory = new File(this.configDirectory);
415 if (!configDirectory.exists()) {
416 if (!configDirectory.mkdirs()) {
417 System.err.println("could not create “" + this.configDirectory + "”!");
421 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
422 System.err.println("can not access “" + this.configDirectory + "”!");
425 File configFile = new File(configDirectory, "swing-interface.properties");
426 Properties configProperties = new Properties();
427 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
428 configProperties.setProperty("antialias", String.valueOf(antialias));
429 if (controlFont != null) {
430 configProperties.setProperty("controlFont", controlFont);
432 if (userFont != null) {
433 configProperties.setProperty("userFont", userFont);
435 if (lookAndFeel != null) {
436 configProperties.setProperty("lookAndFeel", lookAndFeel);
438 configProperties.setProperty("language", I18n.getLocale().getLanguage());
439 FileOutputStream configOutputStream = null;
441 configOutputStream = new FileOutputStream(configFile);
442 configProperties.store(configOutputStream, "configuration of swing interface");
443 } catch (IOException ioe1) {
444 System.err.println("could not save config, " + ioe1.getMessage());
446 Closer.close(configOutputStream);
451 * Initializes all actions.
453 private void initActions() {
454 configureAction = new I18nAction("mainWindow.menu.jSite.configure", IconLoader.loadIcon("/preferences-system.png")) {
459 @SuppressWarnings("synthetic-access")
460 public void actionPerformed(ActionEvent actionEvent) {
464 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
469 @SuppressWarnings("synthetic-access")
470 public void actionPerformed(ActionEvent actionEvent) {
474 quitAction = new I18nAction("mainWindow.menu.jSite.quit", IconLoader.loadIcon("/system-log-out.png")) {
479 @SuppressWarnings("synthetic-access")
480 public void actionPerformed(ActionEvent actionEvent) {
484 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
489 @SuppressWarnings("synthetic-access")
490 public void actionPerformed(ActionEvent actionEvent) {
494 nodeConnectAction = new I18nAction("mainWindow.menu.node.item.connect", false) {
496 @SuppressWarnings("synthetic-access")
497 public void actionPerformed(ActionEvent actionEvent) {
498 List<Node> nodes = core.getNodes();
499 if (nodes.isEmpty()) {
502 nodeConnect(nodes.get(0));
506 nodeDisconnectAction = new I18nAction("mainWindow.menu.node.item.disconnect", false) {
511 @SuppressWarnings("synthetic-access")
512 public void actionPerformed(ActionEvent e) {
513 List<Node> nodes = core.getNodes();
514 if (nodes.isEmpty()) {
517 nodeDisconnect(nodes.get(0));
520 rebuildNodeActions(core.getNodes());
521 List<Locale> availableLanguages = I18n.findAvailableLanguages();
522 for (final Locale locale: availableLanguages) {
523 I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
525 @SuppressWarnings("synthetic-access")
526 public void actionPerformed(ActionEvent e) {
527 changeLanguage(locale, this);
531 if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
532 languageAction.setEnabled(false);
534 languageActions.add(languageAction);
536 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
541 @SuppressWarnings("synthetic-access")
542 public void actionPerformed(ActionEvent actionEvent) {
546 addProjectAction = new I18nAction("mainWindow.button.addProject") {
551 @SuppressWarnings("synthetic-access")
552 public void actionPerformed(ActionEvent actionEvent) {
556 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
561 @SuppressWarnings("synthetic-access")
562 public void actionPerformed(ActionEvent actionEvent) {
566 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
571 @SuppressWarnings("synthetic-access")
572 public void actionPerformed(ActionEvent actionEvent) {
579 * Initializes all child dialogs.
581 private void initDialogs() {
582 manageNodesDialog = new ManageNodesDialog(this);
583 aboutDialog = new AboutDialog(this);
584 configurationDialog = new ConfigurationDialog(this);
592 * Shows the configuration dialog.
594 private void configure() {
595 configurationDialog.setAdvancedMode(advancedMode);
596 configurationDialog.setAntialias(antialias);
597 configurationDialog.setControlFont(controlFont);
598 configurationDialog.setUserFont(userFont);
599 configurationDialog.setLookAndFeel(lookAndFeel);
600 configurationDialog.setVisible(true);
601 if (!configurationDialog.wasCancelled()) {
602 advancedMode = configurationDialog.isAdvancedMode();
603 if (!advancedMode && (nodeList.size() > 1)) {
604 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
606 mainWindow.setAdvancedMode(advancedMode);
607 antialias = configurationDialog.isAntialias();
608 controlFont = configurationDialog.getControlFont();
609 userFont = configurationDialog.getUserFont();
610 lookAndFeel = configurationDialog.getLookAndFeel();
616 * Imports old jSite configuration.
618 private void importConfig() {
625 private void quit() {
631 * Rebuilds all node connect and disconnect actions.
636 private void rebuildNodeActions(List<Node> nodes) {
637 nodeConnectActions.clear();
638 nodeNodeConnectActions.clear();
639 nodeConnectActionNodes.clear();
640 nodeDisconnectActions.clear();
641 nodeNodeDisconnectActions.clear();
642 nodeDisconnectActionNodes.clear();
643 for (Node node: nodes) {
644 Action nodeConnectAction = new AbstractAction(node.getName()) {
649 @SuppressWarnings("synthetic-access")
650 public void actionPerformed(ActionEvent e) {
651 Node node = nodeConnectActionNodes.get(this);
655 nodeConnectActions.add(nodeConnectAction);
656 nodeConnectActionNodes.put(nodeConnectAction, node);
657 nodeNodeConnectActions.put(node, nodeConnectAction);
658 Action nodeDisconnectAction = new AbstractAction(node.getName()) {
663 @SuppressWarnings("synthetic-access")
664 public void actionPerformed(ActionEvent e) {
665 Node node = nodeDisconnectActionNodes.get(this);
666 nodeDisconnect(node);
669 // nodeDisconnectActions.add(nodeDisconnectAction);
670 nodeDisconnectActionNodes.put(nodeDisconnectAction, node);
671 nodeNodeDisconnectActions.put(node, nodeDisconnectAction);
676 * Pops up the “manage nodes” dialog.
678 private void manageNodes() {
680 manageNodesDialog.setNodeList(nodeList);
681 manageNodesDialog.setVisible(true);
682 nodeList = manageNodesDialog.getNodeList();
683 rebuildNodeActions(nodeList);
684 mainWindow.refreshNodeMenuItems();
686 if (nodeList.isEmpty()) {
687 Node newNode = new Node();
688 newNode.setName(I18n.get("general.defaultNode.name"));
689 newNode.setHostname("localhost");
690 newNode.setPort(9481);
691 nodeList.add(newNode);
693 Node firstNode = nodeList.get(0);
694 EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
695 editNodeDialog.setNodeName(firstNode.getName());
696 editNodeDialog.setNodeHostname(firstNode.getHostname());
697 editNodeDialog.setNodePort(firstNode.getPort());
698 editNodeDialog.setVisible(true);
699 if (!editNodeDialog.wasCancelled()) {
700 firstNode.setName(editNodeDialog.getNodeName());
701 firstNode.setHostname(editNodeDialog.getNodeHostname());
702 firstNode.setPort(editNodeDialog.getNodePort());
703 /* TODO - give to core. */
709 * Connects to the node.
712 * The node to connect to
714 private void nodeConnect(final Node node) {
715 threadPool.execute(new Runnable() {
720 @SuppressWarnings("synthetic-access")
722 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
723 core.connectToNode(node);
729 * Disconnects from the node.
732 * The node to disconnect from
734 private void nodeDisconnect(Node node) {
735 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
736 core.disconnectFromNode(node);
740 * Changes the language of the interface. This method also disables the
741 * action for the newly set language and enables all others.
745 * @param languageAction
746 * The action that triggered the change
748 private void changeLanguage(Locale newLocale, I18nAction languageAction) {
749 for (I18nAction i18nAction: languageActions) {
750 i18nAction.setEnabled(i18nAction != languageAction);
752 I18n.setLocale(newLocale);
756 * Shows the “about” dialog.
758 private void helpAbout() {
759 aboutDialog.setVisible(true);
765 private void addProject() {
766 Project project = new Project();
767 project.setName("New Project");
768 project.setDescription("");
774 private void cloneProject() {
781 private void deleteProject() {
786 // INTERFACE CoreListener
792 public void loadingProjectsDone(String directory) {
793 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
799 public void loadingProjectsFailed(String directory, Throwable throwable) {
800 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
806 public void savingProjectsDone(String directory) {
807 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
813 public void savingProjectsFailed(String directory, Throwable throwabled) {
820 public void loadingNodesDone(String directory) {
821 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
827 public void loadingNodesFailed(String directory, Throwable throwable) {
834 public void savingNodesDone(String directory) {
835 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
841 public void savingNodesFailed(String directory, Throwable throwable) {
848 public void coreLoaded() {
849 this.nodeList = core.getNodes();
850 manageNodesDialog.setNodeList(nodeList);
851 mainWindow.setVisible(true);
852 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
858 public void coreStopped() {
859 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
865 public void nodeConnecting(Node node) {
866 Action nodeConnectAction = nodeNodeConnectActions.get(node);
867 nodeConnectActions.remove(nodeConnectAction);
868 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
869 mainWindow.refreshNodeMenuItems();
875 public void nodeConnected(Node node) {
876 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
877 nodeDisconnectActions.add(nodeDisconnectAction);
878 mainWindow.refreshNodeMenuItems();
884 public void nodeDisconnected(Node node, Throwable throwable) {
885 Action nodeConnectAction = nodeNodeConnectActions.get(node);
886 nodeConnectActions.add(nodeConnectAction);
887 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
888 nodeDisconnectActions.remove(nodeDisconnectAction);
889 mainWindow.refreshNodeMenuItems();
893 // INTERFACE LoggingListener
899 public void logged(LogRecord logRecord) {
900 logWindow.logged(logRecord);