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;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
36 import java.util.Properties;
37 import java.util.concurrent.Executor;
38 import java.util.concurrent.Executors;
39 import java.util.logging.Level;
40 import java.util.logging.LogRecord;
41 import java.util.logging.Logger;
43 import javax.swing.JOptionPane;
44 import javax.swing.UIManager;
45 import javax.swing.UnsupportedLookAndFeelException;
47 import net.pterodactylus.jsite.core.Core;
48 import net.pterodactylus.jsite.core.CoreListener;
49 import net.pterodactylus.jsite.core.JSiteException;
50 import net.pterodactylus.jsite.core.Node;
51 import net.pterodactylus.jsite.i18n.I18n;
52 import net.pterodactylus.jsite.i18n.gui.I18nAction;
53 import net.pterodactylus.jsite.project.Project;
54 import net.pterodactylus.util.image.IconLoader;
55 import net.pterodactylus.util.io.Closer;
56 import net.pterodactylus.util.logging.Logging;
57 import net.pterodactylus.util.logging.LoggingListener;
60 * The Swing user interface.
62 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
65 public class SwingInterface implements CoreListener, LoggingListener, PropertyChangeListener {
68 private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
70 /** The application core. */
71 private final Core core;
73 /** The configuration directory. */
74 private final String configDirectory;
76 /** The main window. */
77 private MainWindow mainWindow;
80 private Executor threadPool = Executors.newCachedThreadPool();
82 /** The logger window. */
83 private LogWindow logWindow;
85 /** The “configure” action. */
86 private I18nAction configureAction;
88 /** The “import config” action. */
89 private I18nAction importConfigAction;
91 /** The “quit” action. */
92 private I18nAction quitAction;
94 /** The “manage nodes” action. */
95 private I18nAction manageNodesAction;
97 /** All node menu items. */
98 private Map<Node, I18nAction> nodeConnectActions = Collections.synchronizedMap(new HashMap<Node, I18nAction>());
100 /** All node disconnect actions. */
101 private Map<Node, I18nAction> nodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, I18nAction>());
103 /** The node manager dialog. */
104 private ManageNodesDialog manageNodesDialog;
106 /** All lanugage menu items. */
107 private List<I18nAction> languageActions = new ArrayList<I18nAction>();
109 /** The “about” action. */
110 private I18nAction helpAboutAction;
112 /** The “add project” action. */
113 private I18nAction addProjectAction;
115 /** The “clone project” action. */
116 private I18nAction cloneProjectAction;
118 /** The “delete project” action. */
119 private I18nAction deleteProjectAction;
121 /** The “about” dialog. */
122 private AboutDialog aboutDialog;
124 /** The configuration dialog. */
125 private ConfigurationDialog configurationDialog;
127 /** The list of all defined nodes. */
128 private List<Node> nodeList = Collections.synchronizedList(new ArrayList<Node>());
134 /** The advanced mode. */
135 private boolean advancedMode;
137 /** Whether to antialias the GUI. */
138 private boolean antialias;
140 /** The control font. */
141 private String controlFont;
143 /** The user font. */
144 private String userFont;
146 /** The class name of the look and feel. */
147 private String lookAndFeel;
149 /** X coordinate of the main window. */
150 private int mainWindowX = -1;
152 /** Y coordinate of the main window. */
153 private int mainWindowY = -1;
155 /** Width of the main window. */
156 private int mainWindowWidth = -1;
158 /** Height of the main window. */
159 private int mainWindowHeight = -1;
162 * Creates a new swing interface.
165 * The core to operate on
166 * @param configDirectory
167 * The directory the configuration is stored in
169 public SwingInterface(Core core, String configDirectory) {
171 this.configDirectory = configDirectory;
172 I18n.setLocale(Locale.ENGLISH);
174 if (lookAndFeel != null) {
176 UIManager.setLookAndFeel(lookAndFeel);
177 } catch (ClassNotFoundException cnfe1) {
178 logger.log(Level.WARNING, "could not load look and feel", cnfe1);
179 } catch (InstantiationException ie1) {
180 logger.log(Level.WARNING, "could not load look and feel", ie1);
181 } catch (IllegalAccessException iae1) {
182 logger.log(Level.WARNING, "could not load look and feel", iae1);
183 } catch (UnsupportedLookAndFeelException ulafe1) {
184 logger.log(Level.WARNING, "could not load look and feel", ulafe1);
188 System.setProperty("swing.aatext", "true");
190 if (controlFont != null) {
191 System.setProperty("swing.plaf.metal.controlFont", controlFont);
193 if (userFont != null) {
194 System.setProperty("swing.plaf.metal.userFont", userFont);
198 mainWindow = new MainWindow(this);
199 mainWindow.setAdvancedMode(advancedMode);
200 if ((mainWindowX != -1) && (mainWindowY != -1) && (mainWindowWidth != -1) && (mainWindowHeight != -1)) {
201 mainWindow.setLocation(mainWindowX, mainWindowY);
202 mainWindow.setSize(mainWindowWidth, mainWindowHeight);
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 for the given node.
279 * The node go get the “connect” action for
280 * @return The “connect to node” action
282 I18nAction getNodeConnectAction(Node node) {
283 return nodeConnectActions.get(node);
287 * Returns the “disconnect from node” action for the given node.
290 * The node go get the “disconnect” action for
291 * @return The “disconnect from node” action
293 I18nAction getNodeDisconnectAction(Node node) {
294 return nodeDisconnectActions.get(node);
298 * Returns all language actions.
300 * @return All language actions
302 List<I18nAction> getLanguageActions() {
303 return languageActions;
307 * Returns the “about” action.
309 * @return The “about” action
311 I18nAction getHelpAboutAction() {
312 return helpAboutAction;
316 * Returns the “add project” action.
318 * @return The “add project” action
320 I18nAction getAddProjectAction() {
321 return addProjectAction;
325 * Returns the “clone project” action.
327 * @return The “clone project” action
329 I18nAction getCloneProjectAction() {
330 return cloneProjectAction;
334 * Returns the “delete project” action.
336 * @return The “delete project” action
338 I18nAction getDeleteProjectAction() {
339 return deleteProjectAction;
343 * Returns all currently configured nodes.
345 * @return All configured nodes
347 List<Node> getNodes() {
352 * Returns the thread pool used for off-thread processes.
354 * @return The thread pool
356 Executor getThreadPool() {
373 * Loads the configuration of the interface.
375 private void loadConfig() {
376 /* initialize default stuff. */
378 /* now read config. */
379 File configFile = new File(configDirectory, "swing-interface.properties");
380 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
381 System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
384 Properties configProperties = new Properties();
385 FileInputStream configInputStream = null;
387 configInputStream = new FileInputStream(configFile);
388 configProperties.load(configInputStream);
389 } catch (IOException ioe1) {
390 System.err.println("could not load config, " + ioe1.getMessage());
392 Closer.close(configInputStream);
394 if (configProperties.containsKey("advancedMode")) {
395 advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
397 if (configProperties.containsKey("antialias")) {
398 antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
400 if (configProperties.containsKey("controlFont")) {
401 controlFont = configProperties.getProperty("controlFont");
403 if (configProperties.containsKey("userFont")) {
404 userFont = configProperties.getProperty("userFont");
406 if (configProperties.containsKey("lookAndFeel")) {
407 lookAndFeel = configProperties.getProperty("lookAndFeel");
409 if (configProperties.containsKey("language")) {
410 I18n.setLocale(new Locale(configProperties.getProperty("language")));
412 if (configProperties.containsKey("mainWindowX")) {
413 mainWindowX = Integer.valueOf(configProperties.getProperty("mainWindowX"));
415 if (configProperties.containsKey("mainWindowY")) {
416 mainWindowY = Integer.valueOf(configProperties.getProperty("mainWindowY"));
418 if (configProperties.containsKey("mainWindowWidth")) {
419 mainWindowWidth = Integer.valueOf(configProperties.getProperty("mainWindowWidth"));
421 if (configProperties.containsKey("mainWindowHeight")) {
422 mainWindowHeight = Integer.valueOf(configProperties.getProperty("mainWindowHeight"));
427 * Saves the configuration.
429 private void saveConfig() {
430 File configDirectory = new File(this.configDirectory);
431 if (!configDirectory.exists()) {
432 if (!configDirectory.mkdirs()) {
433 System.err.println("could not create “" + this.configDirectory + "”!");
437 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
438 System.err.println("can not access “" + this.configDirectory + "”!");
441 File configFile = new File(configDirectory, "swing-interface.properties");
442 Properties configProperties = new Properties();
443 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
444 configProperties.setProperty("antialias", String.valueOf(antialias));
445 if (controlFont != null) {
446 configProperties.setProperty("controlFont", controlFont);
448 if (userFont != null) {
449 configProperties.setProperty("userFont", userFont);
451 if (lookAndFeel != null) {
452 configProperties.setProperty("lookAndFeel", lookAndFeel);
454 configProperties.setProperty("language", I18n.getLocale().getLanguage());
455 configProperties.setProperty("mainWindowX", String.valueOf(mainWindowX));
456 configProperties.setProperty("mainWindowY", String.valueOf(mainWindowY));
457 configProperties.setProperty("mainWindowWidth", String.valueOf(mainWindowWidth));
458 configProperties.setProperty("mainWindowHeight", String.valueOf(mainWindowHeight));
459 FileOutputStream configOutputStream = null;
461 configOutputStream = new FileOutputStream(configFile);
462 configProperties.store(configOutputStream, "configuration of swing interface");
463 } catch (IOException ioe1) {
464 System.err.println("could not save config, " + ioe1.getMessage());
466 Closer.close(configOutputStream);
471 * Initializes all actions.
473 private void initActions() {
474 configureAction = new I18nAction("mainWindow.menu.jSite.configure", IconLoader.loadIcon("/preferences-system.png")) {
479 @SuppressWarnings("synthetic-access")
480 public void actionPerformed(ActionEvent actionEvent) {
484 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
489 @SuppressWarnings("synthetic-access")
490 public void actionPerformed(ActionEvent actionEvent) {
494 quitAction = new I18nAction("mainWindow.menu.jSite.quit", IconLoader.loadIcon("/system-log-out.png")) {
499 @SuppressWarnings("synthetic-access")
500 public void actionPerformed(ActionEvent actionEvent) {
504 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
509 @SuppressWarnings("synthetic-access")
510 public void actionPerformed(ActionEvent actionEvent) {
514 List<Locale> availableLanguages = I18n.findAvailableLanguages();
515 for (final Locale locale: availableLanguages) {
516 I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
518 @SuppressWarnings("synthetic-access")
519 public void actionPerformed(ActionEvent e) {
520 changeLanguage(locale, this);
524 if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
525 languageAction.setEnabled(false);
527 languageActions.add(languageAction);
529 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
534 @SuppressWarnings("synthetic-access")
535 public void actionPerformed(ActionEvent actionEvent) {
539 addProjectAction = new I18nAction("mainWindow.button.addProject") {
544 @SuppressWarnings("synthetic-access")
545 public void actionPerformed(ActionEvent actionEvent) {
549 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
554 @SuppressWarnings("synthetic-access")
555 public void actionPerformed(ActionEvent actionEvent) {
559 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
564 @SuppressWarnings("synthetic-access")
565 public void actionPerformed(ActionEvent actionEvent) {
572 * Initializes all child dialogs.
574 private void initDialogs() {
575 manageNodesDialog = new ManageNodesDialog(this);
576 aboutDialog = new AboutDialog(this);
577 configurationDialog = new ConfigurationDialog(this);
585 * Shows the configuration dialog.
587 private void configure() {
588 configurationDialog.setAdvancedMode(advancedMode);
589 configurationDialog.setAntialias(antialias);
590 configurationDialog.setControlFont(controlFont);
591 configurationDialog.setUserFont(userFont);
592 configurationDialog.setLookAndFeel(lookAndFeel);
593 configurationDialog.setVisible(true);
594 if (!configurationDialog.wasCancelled()) {
595 advancedMode = configurationDialog.isAdvancedMode();
596 if (!advancedMode && (nodeList.size() > 1)) {
597 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
599 mainWindow.setAdvancedMode(advancedMode);
600 antialias = configurationDialog.isAntialias();
601 controlFont = configurationDialog.getControlFont();
602 userFont = configurationDialog.getUserFont();
603 lookAndFeel = configurationDialog.getLookAndFeel();
609 * Imports old jSite configuration.
611 private void importConfig() {
618 private void quit() {
621 mainWindowX = mainWindow.getX();
622 mainWindowY = mainWindow.getY();
623 mainWindowWidth = mainWindow.getWidth();
624 mainWindowHeight = mainWindow.getHeight();
630 * Pops up the “manage nodes” dialog.
632 private void manageNodes() {
634 manageNodesDialog.setNodeList(nodeList);
635 manageNodesDialog.setVisible(true);
636 nodeList = manageNodesDialog.getNodeList();
637 /* TODO - notify main window of changes */
639 if (nodeList.isEmpty()) {
640 Node newNode = new Node();
641 newNode.setName(I18n.get("general.defaultNode.name"));
642 newNode.setHostname("localhost");
643 newNode.setPort(9481);
644 nodeList.add(newNode);
646 Node firstNode = nodeList.get(0);
647 EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
648 editNodeDialog.setNodeName(firstNode.getName());
649 editNodeDialog.setNodeHostname(firstNode.getHostname());
650 editNodeDialog.setNodePort(firstNode.getPort());
651 editNodeDialog.setVisible(true);
652 if (!editNodeDialog.wasCancelled()) {
653 firstNode.setName(editNodeDialog.getNodeName());
654 firstNode.setHostname(editNodeDialog.getNodeHostname());
655 firstNode.setPort(editNodeDialog.getNodePort());
656 /* TODO - give to core. */
662 * Connects to the node.
665 * The node to connect to
667 private void nodeConnect(final Node node) {
668 threadPool.execute(new Runnable() {
673 @SuppressWarnings("synthetic-access")
675 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
676 core.connectToNode(node);
682 * Disconnects from the node.
685 * The node to disconnect from
687 private void nodeDisconnect(Node node) {
688 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
689 core.disconnectFromNode(node);
693 * Changes the language of the interface. This method also disables the
694 * action for the newly set language and enables all others.
698 * @param languageAction
699 * The action that triggered the change
701 private void changeLanguage(Locale newLocale, I18nAction languageAction) {
702 for (I18nAction i18nAction: languageActions) {
703 i18nAction.setEnabled(i18nAction != languageAction);
705 I18n.setLocale(newLocale);
709 * Shows the “about” dialog.
711 private void helpAbout() {
712 aboutDialog.setVisible(true);
718 private void addProject() {
720 Project project = core.createProject();
721 project.setName(I18n.get("general.newProject.name"));
722 project.setDescription(I18n.get("general.newProject.description", new Date()));
723 project.setBasePath("");
724 mainWindow.addProject(project, true);
725 } catch (JSiteException nne1) {
726 /* TODO - add i18n */
727 JOptionPane.showMessageDialog(mainWindow, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
728 } catch (IOException e) {
729 /* TODO - add i18n */
730 JOptionPane.showMessageDialog(mainWindow, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
737 private void cloneProject() {
744 private void deleteProject() {
749 // INTERFACE CoreListener
755 public void loadingProjectsDone(String directory) {
756 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
757 for (Project project: core.getProjects()) {
758 mainWindow.addProject(project, false);
765 public void loadingProjectsFailed(String directory, Throwable throwable) {
766 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
772 public void savingProjectsDone(String directory) {
773 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
779 public void savingProjectsFailed(String directory, Throwable throwabled) {
786 public void loadingNodesDone(String directory) {
787 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
793 public void loadingNodesFailed(String directory, Throwable throwable) {
800 public void savingNodesDone(String directory) {
801 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
807 public void savingNodesFailed(String directory, Throwable throwable) {
814 public void coreLoaded() {
815 mainWindow.setVisible(true);
816 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
822 public void coreStopped() {
823 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
829 public void nodeAdded(final Node node) {
830 logger.log(Level.INFO, "node added: " + node);
832 node.addPropertyChangeListener(this);
833 logger.log(Level.FINE, "nodeList.size(): " + nodeList.size());
834 manageNodesDialog.setNodeList(nodeList);
835 nodeConnectActions.put(node, new I18nAction("mainWindow.menu.connect") {
840 @SuppressWarnings("synthetic-access")
841 public void actionPerformed(ActionEvent e) {
845 nodeDisconnectActions.put(node, new I18nAction("mainWindow.menu.disconnect") {
850 @SuppressWarnings("synthetic-access")
851 public void actionPerformed(ActionEvent e) {
852 nodeDisconnect(node);
855 nodeDisconnectActions.get(node).setEnabled(false);
856 mainWindow.addNode(node);
862 public void nodeRemoved(Node node) {
863 logger.log(Level.INFO, "node removed: " + node);
864 nodeList.remove(node);
865 node.removePropertyChangeListener(this);
866 nodeConnectActions.remove(node);
867 nodeDisconnectActions.remove(node);
868 mainWindow.removeNode(node);
874 public void nodeConnecting(Node node) {
875 nodeConnectActions.get(node).setEnabled(false);
876 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
882 public void nodeConnected(Node node) {
883 nodeDisconnectActions.get(node).setEnabled(true);
884 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectedToNode", node.getName(), node.getHostname(), node.getPort()));
890 public void nodeConnectionFailed(Node node, Throwable cause) {
891 nodeConnectActions.get(node).setEnabled(true);
892 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectionToNodeFailed", node.getName(), node.getHostname(), node.getPort(), (cause != null) ? cause.getMessage() : "no reason given"));
893 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.nodeConnectionFailed.message", node.getName(), node.getHostname(), node.getPort(), (cause != null) ? cause.getMessage() : "no reason given"), I18n.get("mainWindow.error.nodeConnectionFailed.title"), JOptionPane.ERROR_MESSAGE);
899 public void nodeDisconnected(Node node, Throwable throwable) {
900 nodeDisconnectActions.get(node).setEnabled(false);
901 nodeConnectActions.get(node).setEnabled(true);
902 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.disconnectedFromNode", node.getName(), node.getHostname(), node.getPort()));
906 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertStarted(net.pterodactylus.jsite.project.Project)
908 public void projectInsertStarted(Project project) {
909 mainWindow.projectInsertStarted(project);
913 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertProgressed(net.pterodactylus.jsite.project.Project,
914 * int, int, int, int, int, boolean)
916 public void projectInsertProgressed(Project project, int totalBlocks, int requiredBlocks, int successfulBlocks, int failedBlocks, int fatallyFailedBlocks, boolean finalizedTotal) {
917 mainWindow.projectInsertProgressed(project, totalBlocks, requiredBlocks, successfulBlocks, failedBlocks, fatallyFailedBlocks, finalizedTotal);
921 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertGeneratedURI(net.pterodactylus.jsite.project.Project,
924 public void projectInsertGeneratedURI(Project project, String uri) {
925 mainWindow.projectInsertGeneratedURI(project);
929 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertFinished(net.pterodactylus.jsite.project.Project,
932 public void projectInsertFinished(Project project, boolean success) {
933 mainWindow.projectInsertFinished(project, success);
937 // INTERFACE LoggingListener
943 public void logged(LogRecord logRecord) {
944 logWindow.logged(logRecord);
948 // INTERFACE PropertyChangeListener
954 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
955 /* do not react to anything (yet). */