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