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