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