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