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