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