first stab add project addition
[jSite2.git] / src / net / pterodactylus / jsite / gui / SwingInterface.java
1 /*
2  * jSite2 - SwingInterface.java -
3  * Copyright © 2008 David Roden
4  *
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.
9  *
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.
14  *
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.
18  */
19
20 package net.pterodactylus.jsite.gui;
21
22 import java.awt.event.ActionEvent;
23 import java.io.File;
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;
32 import java.util.Map;
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;
39
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;
45
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.core.Request;
51 import net.pterodactylus.jsite.i18n.I18n;
52 import net.pterodactylus.jsite.i18n.gui.I18nAction;
53 import net.pterodactylus.util.image.IconLoader;
54 import net.pterodactylus.util.io.Closer;
55 import net.pterodactylus.util.logging.Logging;
56 import net.pterodactylus.util.logging.LoggingListener;
57
58 /**
59  * The Swing user interface.
60  * 
61  * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
62  * @version $Id$
63  */
64 public class SwingInterface implements CoreListener, LoggingListener {
65
66         /** The logger. */
67         private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
68
69         /** The application core. */
70         private final Core core;
71
72         /** The configuration directory. */
73         private final String configDirectory;
74
75         /** The main window. */
76         private MainWindow mainWindow;
77
78         /** Thread pool. */
79         private Executor threadPool = Executors.newCachedThreadPool();
80
81         /** The logger window. */
82         private LogWindow logWindow;
83
84         /** The “configure” action. */
85         private I18nAction configureAction;
86
87         /** The “import config” action. */
88         private I18nAction importConfigAction;
89
90         /** The “quit” action. */
91         private I18nAction quitAction;
92
93         /** The “manage nodes” action. */
94         private I18nAction manageNodesAction;
95
96         /** The “connect to node” (simple mode) action. */
97         private I18nAction nodeConnectAction;
98
99         /** The “disconnect from node” (simple mode) action. */
100         private I18nAction nodeDisconnectAction;
101
102         /** All node menu items. */
103         private List<Action> nodeConnectActions = Collections.synchronizedList(new ArrayList<Action>());
104
105         /** Mapping from nodes to node connect actions. */
106         private Map<Node, Action> nodeNodeConnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
107
108         /** Mapping from node connect actions to nodes. */
109         private Map<Action, Node> nodeConnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
110
111         /** All node disconnect actions. */
112         private List<Action> nodeDisconnectActions = Collections.synchronizedList(new ArrayList<Action>());
113
114         /** Mapping from nodes to node disconnect actions. */
115         private Map<Node, Action> nodeNodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
116
117         /** Mapping from node disconnect actions to nodes. */
118         private Map<Action, Node> nodeDisconnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
119
120         /** The node manager dialog. */
121         private ManageNodesDialog manageNodesDialog;
122
123         /** All lanugage menu items. */
124         private List<I18nAction> languageActions = new ArrayList<I18nAction>();
125
126         /** The “about” action. */
127         private I18nAction helpAboutAction;
128
129         /** The “add project” action. */
130         private I18nAction addProjectAction;
131
132         /** The “clone project” action. */
133         private I18nAction cloneProjectAction;
134
135         /** The “delete project” action. */
136         private I18nAction deleteProjectAction;
137
138         /** The “about” dialog. */
139         private AboutDialog aboutDialog;
140
141         /** The configuration dialog. */
142         private ConfigurationDialog configurationDialog;
143
144         /** The list of all defined nodes. */
145         private List<Node> nodeList = Collections.synchronizedList(new ArrayList<Node>());
146
147         /** The request table model. */
148         private RequestTableModel requestTableModel = new RequestTableModel();
149
150         //
151         // CONFIGURATION
152         //
153
154         /** The advanced mode. */
155         private boolean advancedMode;
156
157         /** Whether to antialias the GUI. */
158         private boolean antialias;
159
160         /** The control font. */
161         private String controlFont;
162
163         /** The user font. */
164         private String userFont;
165
166         /** The class name of the look and feel. */
167         private String lookAndFeel;
168
169         /** X coordinate of the main window. */
170         private int mainWindowX = -1;
171
172         /** Y coordinate of the main window. */
173         private int mainWindowY = -1;
174
175         /** Width of the main window. */
176         private int mainWindowWidth = -1;
177
178         /** Height of the main window. */
179         private int mainWindowHeight = -1;
180
181         /**
182          * Creates a new swing interface.
183          * 
184          * @param core
185          *            The core to operate on
186          * @param configDirectory
187          *            The directory the configuration is stored in
188          */
189         public SwingInterface(Core core, String configDirectory) {
190                 this.core = core;
191                 this.configDirectory = configDirectory;
192                 I18n.setLocale(Locale.ENGLISH);
193                 loadConfig();
194                 if (lookAndFeel != null) {
195                         try {
196                                 UIManager.setLookAndFeel(lookAndFeel);
197                         } catch (ClassNotFoundException cnfe1) {
198                                 logger.log(Level.WARNING, "could not load look and feel", cnfe1);
199                         } catch (InstantiationException ie1) {
200                                 logger.log(Level.WARNING, "could not load look and feel", ie1);
201                         } catch (IllegalAccessException iae1) {
202                                 logger.log(Level.WARNING, "could not load look and feel", iae1);
203                         } catch (UnsupportedLookAndFeelException ulafe1) {
204                                 logger.log(Level.WARNING, "could not load look and feel", ulafe1);
205                         }
206                 }
207                 if (antialias) {
208                         System.setProperty("swing.aatext", "true");
209                 }
210                 if (controlFont != null) {
211                         System.setProperty("swing.plaf.metal.controlFont", controlFont);
212                 }
213                 if (userFont != null) {
214                         System.setProperty("swing.plaf.metal.userFont", userFont);
215                 }
216                 initActions();
217                 initDialogs();
218                 mainWindow = new MainWindow(this);
219                 mainWindow.setAdvancedMode(advancedMode);
220                 if ((mainWindowX != -1) && (mainWindowY != -1) && (mainWindowWidth != -1) && (mainWindowHeight != -1)) {
221                         mainWindow.setLocation(mainWindowX, mainWindowY);
222                         mainWindow.setSize(mainWindowWidth, mainWindowHeight);
223                 }
224                 logWindow = new LogWindow();
225         }
226
227         //
228         // ACCESSORS
229         //
230
231         /**
232          * Returns the core that is controlled by the Swing interface.
233          * 
234          * @return The core
235          */
236         Core getCore() {
237                 return core;
238         }
239
240         /**
241          * Returns the main window of the Swing interface.
242          * 
243          * @return The main window
244          */
245         MainWindow getMainWindow() {
246                 return mainWindow;
247         }
248
249         /**
250          * Returns whether the advanced mode is activated.
251          * 
252          * @return <code>true</code> if the advanced mode is activated,
253          *         <code>false</code> if the simple mode is activated
254          */
255         boolean isAdvancedMode() {
256                 return advancedMode;
257         }
258
259         /**
260          * Returns the “configure” action.
261          * 
262          * @return The “configure” action
263          */
264         I18nAction getConfigureAction() {
265                 return configureAction;
266         }
267
268         /**
269          * Returns the “import config” action.
270          * 
271          * @return The “import config” action
272          */
273         I18nAction getImportConfigAction() {
274                 return importConfigAction;
275         }
276
277         /**
278          * Returns the “quit” action.
279          * 
280          * @return The “quit” action
281          */
282         I18nAction getQuitAction() {
283                 return quitAction;
284         }
285
286         /**
287          * Returns the “manage nodes” action.
288          * 
289          * @return The “manage nodes” action
290          */
291         I18nAction getManageNodesAction() {
292                 return manageNodesAction;
293         }
294
295         /**
296          * Returns the “connect to node” action.
297          * 
298          * @return The “connect to node” action
299          */
300         I18nAction getNodeConnectAction() {
301                 return nodeConnectAction;
302         }
303
304         /**
305          * Returns all “connect node” actions.
306          * 
307          * @return All “connect node” actions
308          */
309         List<Action> getNodeConnectActions() {
310                 return nodeConnectActions;
311         }
312
313         /**
314          * Returns the “disconnect from node” action.
315          * 
316          * @return The “disconnect from node” action
317          */
318         I18nAction getNodeDisconnectAction() {
319                 return nodeDisconnectAction;
320         }
321
322         /**
323          * Returns all “disconnect node” actions.
324          * 
325          * @return All “disconnect node” action
326          */
327         List<Action> getNodeDisconnectActions() {
328                 return nodeDisconnectActions;
329         }
330
331         /**
332          * Returns all language actions.
333          * 
334          * @return All language actions
335          */
336         List<I18nAction> getLanguageActions() {
337                 return languageActions;
338         }
339
340         /**
341          * Returns the “about” action.
342          * 
343          * @return The “about” action
344          */
345         I18nAction getHelpAboutAction() {
346                 return helpAboutAction;
347         }
348
349         /**
350          * Returns the “add project” action.
351          * 
352          * @return The “add project” action
353          */
354         I18nAction getAddProjectAction() {
355                 return addProjectAction;
356         }
357
358         /**
359          * Returns the “clone project” action.
360          * 
361          * @return The “clone project” action
362          */
363         I18nAction getCloneProjectAction() {
364                 return cloneProjectAction;
365         }
366
367         /**
368          * Returns the “delete project” action.
369          * 
370          * @return The “delete project” action
371          */
372         I18nAction getDeleteProjectAction() {
373                 return deleteProjectAction;
374         }
375
376         /**
377          * Returns the request table model.
378          * 
379          * @return The request table model
380          */
381         RequestTableModel getRequestTableModel() {
382                 return requestTableModel;
383         }
384
385         //
386         // ACTIONS
387         //
388
389         //
390         // SERVICE METHODS
391         //
392
393         //
394         // PRIVATE METHODS
395         //
396
397         /**
398          * Loads the configuration of the interface.
399          */
400         private void loadConfig() {
401                 /* initialize default stuff. */
402                 antialias = false;
403                 /* now read config. */
404                 File configFile = new File(configDirectory, "swing-interface.properties");
405                 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
406                         System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
407                         return;
408                 }
409                 Properties configProperties = new Properties();
410                 FileInputStream configInputStream = null;
411                 try {
412                         configInputStream = new FileInputStream(configFile);
413                         configProperties.load(configInputStream);
414                 } catch (IOException ioe1) {
415                         System.err.println("could not load config, " + ioe1.getMessage());
416                 } finally {
417                         Closer.close(configInputStream);
418                 }
419                 if (configProperties.containsKey("advancedMode")) {
420                         advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
421                 }
422                 if (configProperties.containsKey("antialias")) {
423                         antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
424                 }
425                 if (configProperties.containsKey("controlFont")) {
426                         controlFont = configProperties.getProperty("controlFont");
427                 }
428                 if (configProperties.containsKey("userFont")) {
429                         userFont = configProperties.getProperty("userFont");
430                 }
431                 if (configProperties.containsKey("lookAndFeel")) {
432                         lookAndFeel = configProperties.getProperty("lookAndFeel");
433                 }
434                 if (configProperties.containsKey("language")) {
435                         I18n.setLocale(new Locale(configProperties.getProperty("language")));
436                 }
437                 if (configProperties.containsKey("mainWindowX")) {
438                         mainWindowX = Integer.valueOf(configProperties.getProperty("mainWindowX"));
439                 }
440                 if (configProperties.containsKey("mainWindowY")) {
441                         mainWindowY = Integer.valueOf(configProperties.getProperty("mainWindowY"));
442                 }
443                 if (configProperties.containsKey("mainWindowWidth")) {
444                         mainWindowWidth = Integer.valueOf(configProperties.getProperty("mainWindowWidth"));
445                 }
446                 if (configProperties.containsKey("mainWindowHeight")) {
447                         mainWindowHeight = Integer.valueOf(configProperties.getProperty("mainWindowHeight"));
448                 }
449         }
450
451         /**
452          * Saves the configuration.
453          */
454         private void saveConfig() {
455                 File configDirectory = new File(this.configDirectory);
456                 if (!configDirectory.exists()) {
457                         if (!configDirectory.mkdirs()) {
458                                 System.err.println("could not create “" + this.configDirectory + "”!");
459                                 return;
460                         }
461                 }
462                 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
463                         System.err.println("can not access “" + this.configDirectory + "”!");
464                         return;
465                 }
466                 File configFile = new File(configDirectory, "swing-interface.properties");
467                 Properties configProperties = new Properties();
468                 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
469                 configProperties.setProperty("antialias", String.valueOf(antialias));
470                 if (controlFont != null) {
471                         configProperties.setProperty("controlFont", controlFont);
472                 }
473                 if (userFont != null) {
474                         configProperties.setProperty("userFont", userFont);
475                 }
476                 if (lookAndFeel != null) {
477                         configProperties.setProperty("lookAndFeel", lookAndFeel);
478                 }
479                 configProperties.setProperty("language", I18n.getLocale().getLanguage());
480                 configProperties.setProperty("mainWindowX", String.valueOf(mainWindowX));
481                 configProperties.setProperty("mainWindowY", String.valueOf(mainWindowY));
482                 configProperties.setProperty("mainWindowWidth", String.valueOf(mainWindowWidth));
483                 configProperties.setProperty("mainWindowHeight", String.valueOf(mainWindowHeight));
484                 FileOutputStream configOutputStream = null;
485                 try {
486                         configOutputStream = new FileOutputStream(configFile);
487                         configProperties.store(configOutputStream, "configuration of swing interface");
488                 } catch (IOException ioe1) {
489                         System.err.println("could not save config, " + ioe1.getMessage());
490                 } finally {
491                         Closer.close(configOutputStream);
492                 }
493         }
494
495         /**
496          * Initializes all actions.
497          */
498         private void initActions() {
499                 configureAction = new I18nAction("mainWindow.menu.jSite.configure", IconLoader.loadIcon("/preferences-system.png")) {
500
501                         /**
502                          * {@inheritDoc}
503                          */
504                         @SuppressWarnings("synthetic-access")
505                         public void actionPerformed(ActionEvent actionEvent) {
506                                 configure();
507                         }
508                 };
509                 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
510
511                         /**
512                          * {@inheritDoc}
513                          */
514                         @SuppressWarnings("synthetic-access")
515                         public void actionPerformed(ActionEvent actionEvent) {
516                                 importConfig();
517                         }
518                 };
519                 quitAction = new I18nAction("mainWindow.menu.jSite.quit", IconLoader.loadIcon("/system-log-out.png")) {
520
521                         /**
522                          * {@inheritDoc}
523                          */
524                         @SuppressWarnings("synthetic-access")
525                         public void actionPerformed(ActionEvent actionEvent) {
526                                 quit();
527                         }
528                 };
529                 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
530
531                         /**
532                          * {@inheritDoc}
533                          */
534                         @SuppressWarnings("synthetic-access")
535                         public void actionPerformed(ActionEvent actionEvent) {
536                                 manageNodes();
537                         }
538                 };
539                 nodeConnectAction = new I18nAction("mainWindow.menu.node.item.connect", false) {
540
541                         @SuppressWarnings("synthetic-access")
542                         public void actionPerformed(ActionEvent actionEvent) {
543                                 List<Node> nodes = core.getNodes();
544                                 if (nodes.isEmpty()) {
545                                         return;
546                                 }
547                                 nodeConnect(nodes.get(0));
548                         }
549
550                 };
551                 nodeDisconnectAction = new I18nAction("mainWindow.menu.node.item.disconnect", false) {
552
553                         /**
554                          * {@inheritDoc}
555                          */
556                         @SuppressWarnings("synthetic-access")
557                         public void actionPerformed(ActionEvent e) {
558                                 List<Node> nodes = core.getNodes();
559                                 if (nodes.isEmpty()) {
560                                         return;
561                                 }
562                                 nodeDisconnect(nodes.get(0));
563                         }
564                 };
565                 rebuildNodeActions(core.getNodes());
566                 List<Locale> availableLanguages = I18n.findAvailableLanguages();
567                 for (final Locale locale: availableLanguages) {
568                         I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
569
570                                 @SuppressWarnings("synthetic-access")
571                                 public void actionPerformed(ActionEvent e) {
572                                         changeLanguage(locale, this);
573                                 }
574
575                         };
576                         if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
577                                 languageAction.setEnabled(false);
578                         }
579                         languageActions.add(languageAction);
580                 }
581                 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
582
583                         /**
584                          * {@inheritDoc}
585                          */
586                         @SuppressWarnings("synthetic-access")
587                         public void actionPerformed(ActionEvent actionEvent) {
588                                 helpAbout();
589                         }
590                 };
591                 addProjectAction = new I18nAction("mainWindow.button.addProject") {
592
593                         /**
594                          * {@inheritDoc}
595                          */
596                         @SuppressWarnings("synthetic-access")
597                         public void actionPerformed(ActionEvent actionEvent) {
598                                 addProject();
599                         }
600                 };
601                 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
602
603                         /**
604                          * {@inheritDoc}
605                          */
606                         @SuppressWarnings("synthetic-access")
607                         public void actionPerformed(ActionEvent actionEvent) {
608                                 cloneProject();
609                         }
610                 };
611                 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
612
613                         /**
614                          * {@inheritDoc}
615                          */
616                         @SuppressWarnings("synthetic-access")
617                         public void actionPerformed(ActionEvent actionEvent) {
618                                 deleteProject();
619                         }
620                 };
621         }
622
623         /**
624          * Initializes all child dialogs.
625          */
626         private void initDialogs() {
627                 manageNodesDialog = new ManageNodesDialog(this);
628                 aboutDialog = new AboutDialog(this);
629                 configurationDialog = new ConfigurationDialog(this);
630         }
631
632         //
633         // PRIVATE ACTIONS
634         //
635
636         /**
637          * Shows the configuration dialog.
638          */
639         private void configure() {
640                 configurationDialog.setAdvancedMode(advancedMode);
641                 configurationDialog.setAntialias(antialias);
642                 configurationDialog.setControlFont(controlFont);
643                 configurationDialog.setUserFont(userFont);
644                 configurationDialog.setLookAndFeel(lookAndFeel);
645                 configurationDialog.setVisible(true);
646                 if (!configurationDialog.wasCancelled()) {
647                         advancedMode = configurationDialog.isAdvancedMode();
648                         if (!advancedMode && (nodeList.size() > 1)) {
649                                 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
650                         }
651                         mainWindow.setAdvancedMode(advancedMode);
652                         antialias = configurationDialog.isAntialias();
653                         controlFont = configurationDialog.getControlFont();
654                         userFont = configurationDialog.getUserFont();
655                         lookAndFeel = configurationDialog.getLookAndFeel();
656                         saveConfig();
657                 }
658         }
659
660         /**
661          * Imports old jSite configuration.
662          */
663         private void importConfig() {
664                 /* TODO */
665         }
666
667         /**
668          * Quits jSite.
669          */
670         private void quit() {
671                 /* TODO - ask */
672                 core.stop();
673                 mainWindowX = mainWindow.getX();
674                 mainWindowY = mainWindow.getY();
675                 mainWindowWidth = mainWindow.getWidth();
676                 mainWindowHeight = mainWindow.getHeight();
677                 saveConfig();
678                 System.exit(0);
679         }
680
681         /**
682          * Rebuilds all node connect and disconnect actions.
683          * 
684          * @param nodes
685          *            The list of nodes
686          */
687         private void rebuildNodeActions(List<Node> nodes) {
688                 logger.fine("rebuilding node actions…");
689                 nodeConnectActions.clear();
690                 nodeNodeConnectActions.clear();
691                 nodeConnectActionNodes.clear();
692                 nodeDisconnectActions.clear();
693                 nodeNodeDisconnectActions.clear();
694                 nodeDisconnectActionNodes.clear();
695                 for (Node node: nodes) {
696                         logger.finer("adding node “" + node + "” to menus");
697                         Action nodeConnectAction = new AbstractAction(node.getName()) {
698
699                                 /**
700                                  * {@inheritDoc}
701                                  */
702                                 @SuppressWarnings("synthetic-access")
703                                 public void actionPerformed(ActionEvent e) {
704                                         Node node = nodeConnectActionNodes.get(this);
705                                         nodeConnect(node);
706                                 }
707                         };
708                         nodeConnectActions.add(nodeConnectAction);
709                         nodeConnectActionNodes.put(nodeConnectAction, node);
710                         nodeNodeConnectActions.put(node, nodeConnectAction);
711                         Action nodeDisconnectAction = new AbstractAction(node.getName()) {
712
713                                 /**
714                                  * {@inheritDoc}
715                                  */
716                                 @SuppressWarnings("synthetic-access")
717                                 public void actionPerformed(ActionEvent e) {
718                                         Node node = nodeDisconnectActionNodes.get(this);
719                                         nodeDisconnect(node);
720                                 }
721                         };
722                         // nodeDisconnectActions.add(nodeDisconnectAction);
723                         nodeDisconnectActionNodes.put(nodeDisconnectAction, node);
724                         nodeNodeDisconnectActions.put(node, nodeDisconnectAction);
725                 }
726         }
727
728         /**
729          * Pops up the “manage nodes” dialog.
730          */
731         private void manageNodes() {
732                 if (advancedMode) {
733                         manageNodesDialog.setNodeList(nodeList);
734                         manageNodesDialog.setVisible(true);
735                         nodeList = manageNodesDialog.getNodeList();
736                         rebuildNodeActions(nodeList);
737                         mainWindow.refreshNodeMenuItems();
738                 } else {
739                         if (nodeList.isEmpty()) {
740                                 Node newNode = new Node();
741                                 newNode.setName(I18n.get("general.defaultNode.name"));
742                                 newNode.setHostname("localhost");
743                                 newNode.setPort(9481);
744                                 nodeList.add(newNode);
745                         }
746                         Node firstNode = nodeList.get(0);
747                         EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
748                         editNodeDialog.setNodeName(firstNode.getName());
749                         editNodeDialog.setNodeHostname(firstNode.getHostname());
750                         editNodeDialog.setNodePort(firstNode.getPort());
751                         editNodeDialog.setVisible(true);
752                         if (!editNodeDialog.wasCancelled()) {
753                                 firstNode.setName(editNodeDialog.getNodeName());
754                                 firstNode.setHostname(editNodeDialog.getNodeHostname());
755                                 firstNode.setPort(editNodeDialog.getNodePort());
756                                 /* TODO - give to core. */
757                         }
758                 }
759         }
760
761         /**
762          * Connects to the node.
763          * 
764          * @param node
765          *            The node to connect to
766          */
767         private void nodeConnect(final Node node) {
768                 threadPool.execute(new Runnable() {
769
770                         /**
771                          * {@inheritDoc}
772                          */
773                         @SuppressWarnings("synthetic-access")
774                         public void run() {
775                                 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
776                                 core.connectToNode(node);
777                         }
778                 });
779         }
780
781         /**
782          * Disconnects from the node.
783          * 
784          * @param node
785          *            The node to disconnect from
786          */
787         private void nodeDisconnect(Node node) {
788                 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
789                 core.disconnectFromNode(node);
790         }
791
792         /**
793          * Changes the language of the interface. This method also disables the
794          * action for the newly set language and enables all others.
795          * 
796          * @param newLocale
797          *            The new language
798          * @param languageAction
799          *            The action that triggered the change
800          */
801         private void changeLanguage(Locale newLocale, I18nAction languageAction) {
802                 for (I18nAction i18nAction: languageActions) {
803                         i18nAction.setEnabled(i18nAction != languageAction);
804                 }
805                 I18n.setLocale(newLocale);
806         }
807
808         /**
809          * Shows the “about” dialog.
810          */
811         private void helpAbout() {
812                 aboutDialog.setVisible(true);
813         }
814
815         /**
816          * Adds a project.
817          */
818         private void addProject() {
819                 try {
820                         Project project = core.addProject("New Project");
821                         System.out.println("private: " + project.getPrivateKey() + ", public: " + project.getPublicKey());
822                         mainWindow.addProject(project);
823                 } catch (IOException e) {
824                         JOptionPane.showMessageDialog(mainWindow, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
825                 }
826         }
827
828         /**
829          * Clones a project.
830          */
831         private void cloneProject() {
832                 /* TODO */
833         }
834
835         /**
836          * Deletes a project.
837          */
838         private void deleteProject() {
839                 /* TODO */
840         }
841
842         //
843         // INTERFACE CoreListener
844         //
845
846         /**
847          * {@inheritDoc}
848          */
849         public void loadingProjectsDone(String directory) {
850                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
851         }
852
853         /**
854          * {@inheritDoc}
855          */
856         public void loadingProjectsFailed(String directory, Throwable throwable) {
857                 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
858         }
859
860         /**
861          * {@inheritDoc}
862          */
863         public void savingProjectsDone(String directory) {
864                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
865         }
866
867         /**
868          * {@inheritDoc}
869          */
870         public void savingProjectsFailed(String directory, Throwable throwabled) {
871                 /* TODO */
872         }
873
874         /**
875          * {@inheritDoc}
876          */
877         public void loadingNodesDone(String directory) {
878                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
879         }
880
881         /**
882          * {@inheritDoc}
883          */
884         public void loadingNodesFailed(String directory, Throwable throwable) {
885                 /* TODO */
886         }
887
888         /**
889          * {@inheritDoc}
890          */
891         public void savingNodesDone(String directory) {
892                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
893         }
894
895         /**
896          * {@inheritDoc}
897          */
898         public void savingNodesFailed(String directory, Throwable throwable) {
899                 /* TODO */
900         }
901
902         /**
903          * {@inheritDoc}
904          */
905         public void coreLoaded() {
906                 mainWindow.setVisible(true);
907                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
908         }
909
910         /**
911          * {@inheritDoc}
912          */
913         public void coreStopped() {
914                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
915         }
916
917         /**
918          * {@inheritDoc}
919          */
920         public void nodeAdded(Node node) {
921                 logger.log(Level.INFO, "node added: " + node);
922                 nodeList.add(node);
923                 logger.log(Level.FINE, "nodeList.size(): " + nodeList.size());
924                 manageNodesDialog.setNodeList(nodeList);
925                 rebuildNodeActions(nodeList);
926                 mainWindow.refreshNodeMenuItems();
927         }
928
929         /**
930          * {@inheritDoc}
931          */
932         public void nodeRemoved(Node node) {
933                 logger.log(Level.INFO, "node removed: " + node);
934                 nodeList.remove(node);
935                 rebuildNodeActions(nodeList);
936                 mainWindow.refreshNodeMenuItems();
937         }
938
939         /**
940          * {@inheritDoc}
941          */
942         public void nodeConnecting(Node node) {
943                 Action nodeConnectAction = nodeNodeConnectActions.get(node);
944                 nodeConnectActions.remove(nodeConnectAction);
945                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
946                 mainWindow.refreshNodeMenuItems();
947         }
948
949         /**
950          * {@inheritDoc}
951          */
952         public void nodeConnected(Node node) {
953                 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
954                 nodeDisconnectActions.add(nodeDisconnectAction);
955                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectedToNode", node.getName(), node.getHostname(), node.getPort()));
956                 mainWindow.refreshNodeMenuItems();
957         }
958
959         /**
960          * {@inheritDoc}
961          */
962         public void nodeConnectionFailed(Node node, Throwable cause) {
963                 Action nodeConnectAction = nodeNodeConnectActions.get(node);
964                 nodeConnectActions.add(nodeConnectAction);
965                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectionToNodeFailed", node.getName(), node.getHostname(), node.getPort(), (cause != null) ? cause.getMessage() : "no reason given"));
966                 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);
967                 mainWindow.refreshNodeMenuItems();
968         }
969
970         /**
971          * {@inheritDoc}
972          */
973         public void nodeDisconnected(Node node, Throwable throwable) {
974                 Action nodeConnectAction = nodeNodeConnectActions.get(node);
975                 nodeConnectActions.add(nodeConnectAction);
976                 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
977                 nodeDisconnectActions.remove(nodeDisconnectAction);
978                 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.disconnectedFromNode", node.getName(), node.getHostname(), node.getPort()));
979                 mainWindow.refreshNodeMenuItems();
980         }
981
982         /**
983          * {@inheritDoc}
984          */
985         public void requestAdded(Request request) {
986                 logger.log(Level.INFO, "request added to node: " + request + ", " + request.getNode());
987                 /* TODO - implement */
988                 requestTableModel.addRequest(request);
989         }
990
991         /**
992          * {@inheritDoc}
993          */
994         public void requestProgressed(Request request) {
995                 /* TODO - update table model */
996         }
997
998         /**
999          * @see net.pterodactylus.jsite.core.CoreListener#requestRemoved(net.pterodactylus.jsite.core.Request)
1000          */
1001         public void requestRemoved(Request request) {
1002                 requestTableModel.removeRequest(request);
1003         }
1004
1005         //
1006         // INTERFACE LoggingListener
1007         //
1008
1009         /**
1010          * {@inheritDoc}
1011          */
1012         public void logged(LogRecord logRecord) {
1013                 logWindow.logged(logRecord);
1014         }
1015
1016 }