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