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