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;
44 import net.pterodactylus.jsite.core.Core;
45 import net.pterodactylus.jsite.core.CoreListener;
46 import net.pterodactylus.jsite.core.Node;
47 import net.pterodactylus.jsite.core.Project;
48 import net.pterodactylus.jsite.i18n.I18n;
49 import net.pterodactylus.jsite.i18n.gui.I18nAction;
50 import net.pterodactylus.util.io.Closer;
51 import net.pterodactylus.util.logging.Logging;
52 import net.pterodactylus.util.logging.LoggingListener;
55 * The Swing user interface.
57 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
60 public class SwingInterface implements CoreListener, LoggingListener {
63 private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
65 /** The application core. */
66 private final Core core;
68 /** The configuration directory. */
69 private final String configDirectory;
71 /** The main window. */
72 private MainWindow mainWindow;
75 private Executor threadPool = Executors.newCachedThreadPool();
77 /** The logger window. */
78 private LogWindow logWindow;
80 /** The “configure” action. */
81 private I18nAction configureAction;
83 /** The “import config” action. */
84 private I18nAction importConfigAction;
86 /** The “quit” action. */
87 private I18nAction quitAction;
89 /** The “manage nodes” action. */
90 private I18nAction manageNodesAction;
92 /** The “connect to node” (simple mode) action. */
93 private I18nAction nodeConnectAction;
95 /** The “disconnect from node” (simple mode) action. */
96 private I18nAction nodeDisconnectAction;
98 /** All node menu items. */
99 private List<Action> nodeConnectActions = Collections.synchronizedList(new ArrayList<Action>());
101 /** Mapping from nodes to node connect actions. */
102 private Map<Node, Action> nodeNodeConnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
104 /** Mapping from node connect actions to nodes. */
105 private Map<Action, Node> nodeConnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
107 /** All node disconnect actions. */
108 private List<Action> nodeDisconnectActions = Collections.synchronizedList(new ArrayList<Action>());
110 /** Mapping from nodes to node disconnect actions. */
111 private Map<Node, Action> nodeNodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
113 /** Mapping from node disconnect actions to nodes. */
114 private Map<Action, Node> nodeDisconnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
116 /** The node manager dialog. */
117 private ManageNodesDialog manageNodesDialog;
119 /** All lanugage menu items. */
120 private List<I18nAction> languageActions = new ArrayList<I18nAction>();
122 /** The “about” action. */
123 private I18nAction helpAboutAction;
125 /** The “add project” action. */
126 private I18nAction addProjectAction;
128 /** The “clone project” action. */
129 private I18nAction cloneProjectAction;
131 /** The “delete project” action. */
132 private I18nAction deleteProjectAction;
134 /** The “about” dialog. */
135 private AboutDialog aboutDialog;
137 /** The configuration dialog. */
138 private ConfigurationDialog configurationDialog;
140 /** The list of all defined nodes. */
141 private List<Node> nodeList;
147 /** The advanced mode. */
148 private boolean advancedMode;
150 /** Whether to antialias the GUI. */
151 private boolean antialias;
153 /** The control font. */
154 private String controlFont;
156 /** The user font. */
157 private String userFont;
160 * Creates a new swing interface.
163 * The core to operate on
164 * @param configDirectory
165 * The directory the configuration is stored in
167 public SwingInterface(Core core, String configDirectory) {
169 this.configDirectory = configDirectory;
170 I18n.setLocale(Locale.ENGLISH);
173 System.setProperty("swing.aatext", "true");
175 if (controlFont != null) {
176 System.setProperty("swing.plaf.metal.controlFont", controlFont);
178 if (userFont != null) {
179 System.setProperty("swing.plaf.metal.userFont", userFont);
183 mainWindow = new MainWindow(this);
184 mainWindow.setAdvancedMode(advancedMode);
185 logWindow = new LogWindow();
193 * Returns the core that is controlled by the Swing interface.
202 * Returns the main window of the Swing interface.
204 * @return The main window
206 MainWindow getMainWindow() {
211 * Returns whether the advanced mode is activated.
213 * @return <code>true</code> if the advanced mode is activated,
214 * <code>false</code> if the simple mode is activated
216 boolean isAdvancedMode() {
221 * Returns the “configure” action.
223 * @return The “configure” action
225 I18nAction getConfigureAction() {
226 return configureAction;
230 * Returns the “import config” action.
232 * @return The “import config” action
234 I18nAction getImportConfigAction() {
235 return importConfigAction;
239 * Returns the “quit” action.
241 * @return The “quit” action
243 I18nAction getQuitAction() {
248 * Returns the “manage nodes” action.
250 * @return The “manage nodes” action
252 I18nAction getManageNodesAction() {
253 return manageNodesAction;
257 * Returns the “connect to node” action.
259 * @return The “connect to node” action
261 I18nAction getNodeConnectAction() {
262 return nodeConnectAction;
266 * Returns all “connect node” actions.
268 * @return All “connect node” actions
270 List<Action> getNodeConnectActions() {
271 return nodeConnectActions;
275 * Returns the “disconnect from node” action.
277 * @return The “disconnect from node” action
279 I18nAction getNodeDisconnectAction() {
280 return nodeDisconnectAction;
284 * Returns all “disconnect node” actions.
286 * @return All “disconnect node” action
288 List<Action> getNodeDisconnectActions() {
289 return nodeDisconnectActions;
293 * Returns all language actions.
295 * @return All language actions
297 List<I18nAction> getLanguageActions() {
298 return languageActions;
302 * Returns the “about” action.
304 * @return The “about” action
306 I18nAction getHelpAboutAction() {
307 return helpAboutAction;
311 * Returns the “add project” action.
313 * @return The “add project” action
315 I18nAction getAddProjectAction() {
316 return addProjectAction;
320 * Returns the “clone project” action.
322 * @return The “clone project” action
324 I18nAction getCloneProjectAction() {
325 return cloneProjectAction;
329 * Returns the “delete project” action.
331 * @return The “delete project” action
333 I18nAction getDeleteProjectAction() {
334 return deleteProjectAction;
350 * Loads the configuration of the interface.
352 private void loadConfig() {
353 /* initialize default stuff. */
355 /* now read config. */
356 File configFile = new File(configDirectory, "swing-interface.properties");
357 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
358 System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
361 Properties configProperties = new Properties();
362 FileInputStream configInputStream = null;
364 configInputStream = new FileInputStream(configFile);
365 configProperties.load(configInputStream);
366 } catch (IOException ioe1) {
367 System.err.println("could not load config, " + ioe1.getMessage());
369 Closer.close(configInputStream);
371 if (configProperties.containsKey("advancedMode")) {
372 advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
374 if (configProperties.containsKey("antialias")) {
375 antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
377 if (configProperties.containsKey("controlFont")) {
378 controlFont = configProperties.getProperty("controlFont");
380 if (configProperties.containsKey("userFont")) {
381 userFont = configProperties.getProperty("userFont");
383 if (configProperties.containsKey("language")) {
384 I18n.setLocale(new Locale(configProperties.getProperty("language")));
389 * Saves the configuration.
391 private void saveConfig() {
392 File configDirectory = new File(this.configDirectory);
393 if (!configDirectory.exists()) {
394 if (!configDirectory.mkdirs()) {
395 System.err.println("could not create “" + this.configDirectory + "”!");
399 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
400 System.err.println("can not access “" + this.configDirectory + "”!");
403 File configFile = new File(configDirectory, "swing-interface.properties");
404 Properties configProperties = new Properties();
405 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
406 configProperties.setProperty("antialias", String.valueOf(antialias));
407 if (controlFont != null) {
408 configProperties.setProperty("controlFont", controlFont);
410 if (userFont != null) {
411 configProperties.setProperty("userFont", userFont);
413 configProperties.setProperty("language", I18n.getLocale().getLanguage());
414 FileOutputStream configOutputStream = null;
416 configOutputStream = new FileOutputStream(configFile);
417 configProperties.store(configOutputStream, "configuration of swing interface");
418 } catch (IOException ioe1) {
419 System.err.println("could not save config, " + ioe1.getMessage());
421 Closer.close(configOutputStream);
426 * Initializes all actions.
428 private void initActions() {
429 configureAction = new I18nAction("mainWindow.menu.jSite.configure") {
434 @SuppressWarnings("synthetic-access")
435 public void actionPerformed(ActionEvent actionEvent) {
439 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
444 @SuppressWarnings("synthetic-access")
445 public void actionPerformed(ActionEvent actionEvent) {
449 quitAction = new I18nAction("mainWindow.menu.jSite.quit") {
454 @SuppressWarnings("synthetic-access")
455 public void actionPerformed(ActionEvent actionEvent) {
459 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
464 @SuppressWarnings("synthetic-access")
465 public void actionPerformed(ActionEvent actionEvent) {
469 nodeConnectAction = new I18nAction("mainWindow.menu.node.item.connect", false) {
471 @SuppressWarnings("synthetic-access")
472 public void actionPerformed(ActionEvent actionEvent) {
473 List<Node> nodes = core.getNodes();
474 if (nodes.isEmpty()) {
477 nodeConnect(nodes.get(0));
481 nodeDisconnectAction = new I18nAction("mainWindow.menu.node.item.disconnect", false) {
486 @SuppressWarnings("synthetic-access")
487 public void actionPerformed(ActionEvent e) {
488 List<Node> nodes = core.getNodes();
489 if (nodes.isEmpty()) {
492 nodeDisconnect(nodes.get(0));
495 rebuildNodeActions(core.getNodes());
496 List<Locale> availableLanguages = I18n.findAvailableLanguages();
497 for (final Locale locale: availableLanguages) {
498 I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
500 @SuppressWarnings("synthetic-access")
501 public void actionPerformed(ActionEvent e) {
502 changeLanguage(locale, this);
506 if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
507 languageAction.setEnabled(false);
509 languageActions.add(languageAction);
511 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
516 @SuppressWarnings("synthetic-access")
517 public void actionPerformed(ActionEvent actionEvent) {
521 addProjectAction = new I18nAction("mainWindow.button.addProject") {
526 @SuppressWarnings("synthetic-access")
527 public void actionPerformed(ActionEvent actionEvent) {
531 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
536 @SuppressWarnings("synthetic-access")
537 public void actionPerformed(ActionEvent actionEvent) {
541 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
546 @SuppressWarnings("synthetic-access")
547 public void actionPerformed(ActionEvent actionEvent) {
554 * Initializes all child dialogs.
556 private void initDialogs() {
557 manageNodesDialog = new ManageNodesDialog(this);
558 aboutDialog = new AboutDialog(this);
559 configurationDialog = new ConfigurationDialog(this);
567 * Shows the configuration dialog.
569 private void configure() {
570 configurationDialog.setAdvancedMode(advancedMode);
571 configurationDialog.setAntialias(antialias);
572 configurationDialog.setControlFont(controlFont);
573 configurationDialog.setUserFont(userFont);
574 configurationDialog.setVisible(true);
575 if (!configurationDialog.wasCancelled()) {
576 advancedMode = configurationDialog.isAdvancedMode();
577 if (!advancedMode && (nodeList.size() > 1)) {
578 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
580 mainWindow.setAdvancedMode(advancedMode);
581 antialias = configurationDialog.isAntialias();
582 controlFont = configurationDialog.getControlFont();
583 userFont = configurationDialog.getUserFont();
589 * Imports old jSite configuration.
591 private void importConfig() {
598 private void quit() {
604 * Rebuilds all node connect and disconnect actions.
609 private void rebuildNodeActions(List<Node> nodes) {
610 nodeConnectActions.clear();
611 nodeNodeConnectActions.clear();
612 nodeConnectActionNodes.clear();
613 nodeDisconnectActions.clear();
614 nodeNodeDisconnectActions.clear();
615 nodeDisconnectActionNodes.clear();
616 for (Node node: nodes) {
617 Action nodeConnectAction = new AbstractAction(node.getName()) {
622 @SuppressWarnings("synthetic-access")
623 public void actionPerformed(ActionEvent e) {
624 Node node = nodeConnectActionNodes.get(this);
628 nodeConnectActions.add(nodeConnectAction);
629 nodeConnectActionNodes.put(nodeConnectAction, node);
630 nodeNodeConnectActions.put(node, nodeConnectAction);
631 Action nodeDisconnectAction = new AbstractAction(node.getName()) {
636 @SuppressWarnings("synthetic-access")
637 public void actionPerformed(ActionEvent e) {
638 Node node = nodeDisconnectActionNodes.get(this);
639 nodeDisconnect(node);
642 // nodeDisconnectActions.add(nodeDisconnectAction);
643 nodeDisconnectActionNodes.put(nodeDisconnectAction, node);
644 nodeNodeDisconnectActions.put(node, nodeDisconnectAction);
649 * Pops up the “manage nodes” dialog.
651 private void manageNodes() {
653 manageNodesDialog.setNodeList(nodeList);
654 manageNodesDialog.setVisible(true);
655 nodeList = manageNodesDialog.getNodeList();
656 rebuildNodeActions(nodeList);
657 mainWindow.refreshNodeMenuItems();
659 if (nodeList.isEmpty()) {
660 Node newNode = new Node();
661 newNode.setName(I18n.get("general.defaultNode.name"));
662 newNode.setHostname("localhost");
663 newNode.setPort(9481);
664 nodeList.add(newNode);
666 Node firstNode = nodeList.get(0);
667 EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
668 editNodeDialog.setNodeName(firstNode.getName());
669 editNodeDialog.setNodeHostname(firstNode.getHostname());
670 editNodeDialog.setNodePort(firstNode.getPort());
671 editNodeDialog.setVisible(true);
672 if (!editNodeDialog.wasCancelled()) {
673 firstNode.setName(editNodeDialog.getNodeName());
674 firstNode.setHostname(editNodeDialog.getNodeHostname());
675 firstNode.setPort(editNodeDialog.getNodePort());
676 /* TODO - give to core. */
682 * Connects to the node.
685 * The node to connect to
687 private void nodeConnect(final Node node) {
688 threadPool.execute(new Runnable() {
693 @SuppressWarnings("synthetic-access")
695 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
696 core.connectToNode(node);
702 * Disconnects from the node.
705 * The node to disconnect from
707 private void nodeDisconnect(Node node) {
708 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
709 core.disconnectFromNode(node);
713 * Changes the language of the interface. This method also disables the
714 * action for the newly set language and enables all others.
718 * @param languageAction
719 * The action that triggered the change
721 private void changeLanguage(Locale newLocale, I18nAction languageAction) {
722 for (I18nAction i18nAction: languageActions) {
723 i18nAction.setEnabled(i18nAction != languageAction);
725 I18n.setLocale(newLocale);
729 * Shows the “about” dialog.
731 private void helpAbout() {
732 aboutDialog.setVisible(true);
738 private void addProject() {
739 Project project = new Project();
740 project.setName("New Project");
741 project.setDescription("");
747 private void cloneProject() {
754 private void deleteProject() {
759 // INTERFACE CoreListener
765 public void loadingProjectsDone(String directory) {
766 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
772 public void loadingProjectsFailed(String directory, Throwable throwable) {
773 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
779 public void savingProjectsDone(String directory) {
780 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
786 public void savingProjectsFailed(String directory, Throwable throwabled) {
793 public void loadingNodesDone(String directory) {
794 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
800 public void loadingNodesFailed(String directory, Throwable throwable) {
807 public void savingNodesDone(String directory) {
808 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
814 public void savingNodesFailed(String directory, Throwable throwable) {
821 public void coreLoaded() {
822 this.nodeList = core.getNodes();
823 manageNodesDialog.setNodeList(nodeList);
824 mainWindow.setVisible(true);
825 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
831 public void coreStopped() {
832 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
838 public void nodeConnecting(Node node) {
839 Action nodeConnectAction = nodeNodeConnectActions.get(node);
840 nodeConnectActions.remove(nodeConnectAction);
841 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
842 mainWindow.refreshNodeMenuItems();
848 public void nodeConnected(Node node) {
849 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
850 nodeDisconnectActions.add(nodeDisconnectAction);
851 mainWindow.refreshNodeMenuItems();
857 public void nodeDisconnected(Node node, Throwable throwable) {
858 Action nodeConnectAction = nodeNodeConnectActions.get(node);
859 nodeConnectActions.add(nodeConnectAction);
860 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
861 nodeDisconnectActions.remove(nodeDisconnectAction);
862 mainWindow.refreshNodeMenuItems();
866 // INTERFACE LoggingListener
872 public void logged(LogRecord logRecord) {
873 logWindow.logged(logRecord);