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