2 * jSite2 - SwingInterface.java -
3 * Copyright © 2008 David Roden
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.
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.
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.
20 package net.pterodactylus.jsite.gui;
22 import java.awt.event.ActionEvent;
23 import java.beans.PropertyChangeEvent;
24 import java.beans.PropertyChangeListener;
26 import java.io.FileInputStream;
27 import java.io.FileOutputStream;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.List;
34 import java.util.Locale;
36 import java.util.Properties;
37 import java.util.concurrent.Executor;
38 import java.util.concurrent.Executors;
39 import java.util.logging.Level;
40 import java.util.logging.LogRecord;
41 import java.util.logging.Logger;
43 import javax.swing.Action;
44 import javax.swing.JOptionPane;
45 import javax.swing.UIManager;
46 import javax.swing.UnsupportedLookAndFeelException;
48 import net.pterodactylus.jsite.core.Core;
49 import net.pterodactylus.jsite.core.CoreListener;
50 import net.pterodactylus.jsite.core.JSiteException;
51 import net.pterodactylus.jsite.core.Node;
52 import net.pterodactylus.jsite.i18n.I18n;
53 import net.pterodactylus.jsite.i18n.gui.I18nAction;
54 import net.pterodactylus.jsite.project.Project;
55 import net.pterodactylus.util.image.IconLoader;
56 import net.pterodactylus.util.io.Closer;
57 import net.pterodactylus.util.logging.Logging;
58 import net.pterodactylus.util.logging.LoggingListener;
61 * The Swing user interface.
63 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
66 public class SwingInterface implements CoreListener, LoggingListener, PropertyChangeListener {
69 private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
71 /** The application core. */
72 private final Core core;
74 /** The configuration directory. */
75 private final String configDirectory;
77 /** The main window. */
78 private MainWindow mainWindow;
81 private Executor threadPool = Executors.newCachedThreadPool();
83 /** The logger window. */
84 private LogWindow logWindow;
86 /** The “configure” action. */
87 private I18nAction configureAction;
89 /** The “import config” action. */
90 private I18nAction importConfigAction;
92 /** The “quit” action. */
93 private I18nAction quitAction;
95 /** The “manage nodes” action. */
96 private I18nAction manageNodesAction;
98 /** All node menu items. */
99 private Map<Node, I18nAction> nodeConnectActions = Collections.synchronizedMap(new HashMap<Node, I18nAction>());
101 /** All node disconnect actions. */
102 private Map<Node, I18nAction> nodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, I18nAction>());
104 /** The node manager dialog. */
105 private ManageNodesDialog manageNodesDialog;
107 /** All lanugage menu items. */
108 private List<I18nAction> languageActions = new ArrayList<I18nAction>();
110 /** The “about” action. */
111 private I18nAction helpAboutAction;
113 /** The “add project” action. */
114 private I18nAction addProjectAction;
116 /** The “clone project” action. */
117 private I18nAction cloneProjectAction;
119 /** The “delete project” action. */
120 private I18nAction deleteProjectAction;
122 /** The “about” dialog. */
123 private AboutDialog aboutDialog;
125 /** The configuration dialog. */
126 private ConfigurationDialog configurationDialog;
128 /** The list of all defined nodes. */
129 private List<Node> nodeList = Collections.synchronizedList(new ArrayList<Node>());
135 /** The advanced mode. */
136 private boolean advancedMode;
138 /** Whether to antialias the GUI. */
139 private boolean antialias;
141 /** The control font. */
142 private String controlFont;
144 /** The user font. */
145 private String userFont;
147 /** The class name of the look and feel. */
148 private String lookAndFeel;
150 /** X coordinate of the main window. */
151 private int mainWindowX = -1;
153 /** Y coordinate of the main window. */
154 private int mainWindowY = -1;
156 /** Width of the main window. */
157 private int mainWindowWidth = -1;
159 /** Height of the main window. */
160 private int mainWindowHeight = -1;
163 * Creates a new swing interface.
166 * The core to operate on
167 * @param configDirectory
168 * The directory the configuration is stored in
170 public SwingInterface(Core core, String configDirectory) {
172 this.configDirectory = configDirectory;
173 I18n.setLocale(Locale.ENGLISH);
175 if (lookAndFeel != null) {
177 UIManager.setLookAndFeel(lookAndFeel);
178 } catch (ClassNotFoundException cnfe1) {
179 logger.log(Level.WARNING, "could not load look and feel", cnfe1);
180 } catch (InstantiationException ie1) {
181 logger.log(Level.WARNING, "could not load look and feel", ie1);
182 } catch (IllegalAccessException iae1) {
183 logger.log(Level.WARNING, "could not load look and feel", iae1);
184 } catch (UnsupportedLookAndFeelException ulafe1) {
185 logger.log(Level.WARNING, "could not load look and feel", ulafe1);
189 System.setProperty("swing.aatext", "true");
191 if (controlFont != null) {
192 System.setProperty("swing.plaf.metal.controlFont", controlFont);
194 if (userFont != null) {
195 System.setProperty("swing.plaf.metal.userFont", userFont);
199 mainWindow = new MainWindow(this);
200 mainWindow.setAdvancedMode(advancedMode);
201 if ((mainWindowX != -1) && (mainWindowY != -1) && (mainWindowWidth != -1) && (mainWindowHeight != -1)) {
202 mainWindow.setLocation(mainWindowX, mainWindowY);
203 mainWindow.setSize(mainWindowWidth, mainWindowHeight);
205 logWindow = new LogWindow();
213 * Returns the core that is controlled by the Swing interface.
222 * Returns the main window of the Swing interface.
224 * @return The main window
226 MainWindow getMainWindow() {
231 * Returns whether the advanced mode is activated.
233 * @return <code>true</code> if the advanced mode is activated,
234 * <code>false</code> if the simple mode is activated
236 boolean isAdvancedMode() {
241 * Returns the “configure” action.
243 * @return The “configure” action
245 I18nAction getConfigureAction() {
246 return configureAction;
250 * Returns the “import config” action.
252 * @return The “import config” action
254 I18nAction getImportConfigAction() {
255 return importConfigAction;
259 * Returns the “quit” action.
261 * @return The “quit” action
263 I18nAction getQuitAction() {
268 * Returns the “manage nodes” action.
270 * @return The “manage nodes” action
272 I18nAction getManageNodesAction() {
273 return manageNodesAction;
277 * Returns the “connect to node” action for the given node.
280 * The node go get the “connect” action for
281 * @return The “connect to node” action
283 I18nAction getNodeConnectAction(Node node) {
284 return nodeConnectActions.get(node);
288 * Returns the “disconnect from node” action for the given node.
291 * The node go get the “disconnect” action for
292 * @return The “disconnect from node” action
294 I18nAction getNodeDisconnectAction(Node node) {
295 return nodeDisconnectActions.get(node);
299 * Returns all language actions.
301 * @return All language actions
303 List<I18nAction> getLanguageActions() {
304 return languageActions;
308 * Returns the “about” action.
310 * @return The “about” action
312 I18nAction getHelpAboutAction() {
313 return helpAboutAction;
317 * Returns the “add project” action.
319 * @return The “add project” action
321 I18nAction getAddProjectAction() {
322 return addProjectAction;
326 * Returns the “clone project” action.
328 * @return The “clone project” action
330 I18nAction getCloneProjectAction() {
331 return cloneProjectAction;
335 * Returns the “delete project” action.
337 * @return The “delete project” action
339 I18nAction getDeleteProjectAction() {
340 return deleteProjectAction;
344 * Returns all currently configured nodes.
346 * @return All configured nodes
348 List<Node> getNodes() {
365 * Loads the configuration of the interface.
367 private void loadConfig() {
368 /* initialize default stuff. */
370 /* now read config. */
371 File configFile = new File(configDirectory, "swing-interface.properties");
372 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
373 System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
376 Properties configProperties = new Properties();
377 FileInputStream configInputStream = null;
379 configInputStream = new FileInputStream(configFile);
380 configProperties.load(configInputStream);
381 } catch (IOException ioe1) {
382 System.err.println("could not load config, " + ioe1.getMessage());
384 Closer.close(configInputStream);
386 if (configProperties.containsKey("advancedMode")) {
387 advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
389 if (configProperties.containsKey("antialias")) {
390 antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
392 if (configProperties.containsKey("controlFont")) {
393 controlFont = configProperties.getProperty("controlFont");
395 if (configProperties.containsKey("userFont")) {
396 userFont = configProperties.getProperty("userFont");
398 if (configProperties.containsKey("lookAndFeel")) {
399 lookAndFeel = configProperties.getProperty("lookAndFeel");
401 if (configProperties.containsKey("language")) {
402 I18n.setLocale(new Locale(configProperties.getProperty("language")));
404 if (configProperties.containsKey("mainWindowX")) {
405 mainWindowX = Integer.valueOf(configProperties.getProperty("mainWindowX"));
407 if (configProperties.containsKey("mainWindowY")) {
408 mainWindowY = Integer.valueOf(configProperties.getProperty("mainWindowY"));
410 if (configProperties.containsKey("mainWindowWidth")) {
411 mainWindowWidth = Integer.valueOf(configProperties.getProperty("mainWindowWidth"));
413 if (configProperties.containsKey("mainWindowHeight")) {
414 mainWindowHeight = Integer.valueOf(configProperties.getProperty("mainWindowHeight"));
419 * Saves the configuration.
421 private void saveConfig() {
422 File configDirectory = new File(this.configDirectory);
423 if (!configDirectory.exists()) {
424 if (!configDirectory.mkdirs()) {
425 System.err.println("could not create “" + this.configDirectory + "”!");
429 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
430 System.err.println("can not access “" + this.configDirectory + "”!");
433 File configFile = new File(configDirectory, "swing-interface.properties");
434 Properties configProperties = new Properties();
435 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
436 configProperties.setProperty("antialias", String.valueOf(antialias));
437 if (controlFont != null) {
438 configProperties.setProperty("controlFont", controlFont);
440 if (userFont != null) {
441 configProperties.setProperty("userFont", userFont);
443 if (lookAndFeel != null) {
444 configProperties.setProperty("lookAndFeel", lookAndFeel);
446 configProperties.setProperty("language", I18n.getLocale().getLanguage());
447 configProperties.setProperty("mainWindowX", String.valueOf(mainWindowX));
448 configProperties.setProperty("mainWindowY", String.valueOf(mainWindowY));
449 configProperties.setProperty("mainWindowWidth", String.valueOf(mainWindowWidth));
450 configProperties.setProperty("mainWindowHeight", String.valueOf(mainWindowHeight));
451 FileOutputStream configOutputStream = null;
453 configOutputStream = new FileOutputStream(configFile);
454 configProperties.store(configOutputStream, "configuration of swing interface");
455 } catch (IOException ioe1) {
456 System.err.println("could not save config, " + ioe1.getMessage());
458 Closer.close(configOutputStream);
463 * Initializes all actions.
465 private void initActions() {
466 configureAction = new I18nAction("mainWindow.menu.jSite.configure", IconLoader.loadIcon("/preferences-system.png")) {
471 @SuppressWarnings("synthetic-access")
472 public void actionPerformed(ActionEvent actionEvent) {
476 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
481 @SuppressWarnings("synthetic-access")
482 public void actionPerformed(ActionEvent actionEvent) {
486 quitAction = new I18nAction("mainWindow.menu.jSite.quit", IconLoader.loadIcon("/system-log-out.png")) {
491 @SuppressWarnings("synthetic-access")
492 public void actionPerformed(ActionEvent actionEvent) {
496 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
501 @SuppressWarnings("synthetic-access")
502 public void actionPerformed(ActionEvent actionEvent) {
506 List<Locale> availableLanguages = I18n.findAvailableLanguages();
507 for (final Locale locale: availableLanguages) {
508 I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
510 @SuppressWarnings("synthetic-access")
511 public void actionPerformed(ActionEvent e) {
512 changeLanguage(locale, this);
516 if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
517 languageAction.setEnabled(false);
519 languageActions.add(languageAction);
521 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
526 @SuppressWarnings("synthetic-access")
527 public void actionPerformed(ActionEvent actionEvent) {
531 addProjectAction = new I18nAction("mainWindow.button.addProject") {
536 @SuppressWarnings("synthetic-access")
537 public void actionPerformed(ActionEvent actionEvent) {
541 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
546 @SuppressWarnings("synthetic-access")
547 public void actionPerformed(ActionEvent actionEvent) {
551 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
556 @SuppressWarnings("synthetic-access")
557 public void actionPerformed(ActionEvent actionEvent) {
564 * Initializes all child dialogs.
566 private void initDialogs() {
567 manageNodesDialog = new ManageNodesDialog(this);
568 aboutDialog = new AboutDialog(this);
569 configurationDialog = new ConfigurationDialog(this);
577 * Shows the configuration dialog.
579 private void configure() {
580 configurationDialog.setAdvancedMode(advancedMode);
581 configurationDialog.setAntialias(antialias);
582 configurationDialog.setControlFont(controlFont);
583 configurationDialog.setUserFont(userFont);
584 configurationDialog.setLookAndFeel(lookAndFeel);
585 configurationDialog.setVisible(true);
586 if (!configurationDialog.wasCancelled()) {
587 advancedMode = configurationDialog.isAdvancedMode();
588 if (!advancedMode && (nodeList.size() > 1)) {
589 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
591 mainWindow.setAdvancedMode(advancedMode);
592 antialias = configurationDialog.isAntialias();
593 controlFont = configurationDialog.getControlFont();
594 userFont = configurationDialog.getUserFont();
595 lookAndFeel = configurationDialog.getLookAndFeel();
601 * Imports old jSite configuration.
603 private void importConfig() {
610 private void quit() {
613 mainWindowX = mainWindow.getX();
614 mainWindowY = mainWindow.getY();
615 mainWindowWidth = mainWindow.getWidth();
616 mainWindowHeight = mainWindow.getHeight();
622 * Pops up the “manage nodes” dialog.
624 private void manageNodes() {
626 manageNodesDialog.setNodeList(nodeList);
627 manageNodesDialog.setVisible(true);
628 nodeList = manageNodesDialog.getNodeList();
629 /* TODO - notify main window of changes */
631 if (nodeList.isEmpty()) {
632 Node newNode = new Node();
633 newNode.setName(I18n.get("general.defaultNode.name"));
634 newNode.setHostname("localhost");
635 newNode.setPort(9481);
636 nodeList.add(newNode);
638 Node firstNode = nodeList.get(0);
639 EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
640 editNodeDialog.setNodeName(firstNode.getName());
641 editNodeDialog.setNodeHostname(firstNode.getHostname());
642 editNodeDialog.setNodePort(firstNode.getPort());
643 editNodeDialog.setVisible(true);
644 if (!editNodeDialog.wasCancelled()) {
645 firstNode.setName(editNodeDialog.getNodeName());
646 firstNode.setHostname(editNodeDialog.getNodeHostname());
647 firstNode.setPort(editNodeDialog.getNodePort());
648 /* TODO - give to core. */
654 * Connects to the node.
657 * The node to connect to
659 private void nodeConnect(final Node node) {
660 threadPool.execute(new Runnable() {
665 @SuppressWarnings("synthetic-access")
667 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
668 core.connectToNode(node);
674 * Disconnects from the node.
677 * The node to disconnect from
679 private void nodeDisconnect(Node node) {
680 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
681 core.disconnectFromNode(node);
685 * Changes the language of the interface. This method also disables the
686 * action for the newly set language and enables all others.
690 * @param languageAction
691 * The action that triggered the change
693 private void changeLanguage(Locale newLocale, I18nAction languageAction) {
694 for (I18nAction i18nAction: languageActions) {
695 i18nAction.setEnabled(i18nAction != languageAction);
697 I18n.setLocale(newLocale);
701 * Shows the “about” dialog.
703 private void helpAbout() {
704 aboutDialog.setVisible(true);
710 private void addProject() {
712 Project project = core.createProject();
713 project.setName(I18n.get("general.newProject.name"));
714 project.setDescription(I18n.get("general.newProject.description", new Date()));
715 mainWindow.addProject(project, true);
716 } catch (JSiteException nne1) {
717 /* TODO - add i18n */
718 JOptionPane.showMessageDialog(mainWindow, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
719 } catch (IOException e) {
720 /* TODO - add i18n */
721 JOptionPane.showMessageDialog(mainWindow, I18n.get(""), I18n.get(""), JOptionPane.ERROR_MESSAGE);
728 private void cloneProject() {
735 private void deleteProject() {
740 // INTERFACE CoreListener
746 public void loadingProjectsDone(String directory) {
747 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
748 for (Project project: core.getProjects()) {
749 mainWindow.addProject(project, false);
756 public void loadingProjectsFailed(String directory, Throwable throwable) {
757 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
763 public void savingProjectsDone(String directory) {
764 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
770 public void savingProjectsFailed(String directory, Throwable throwabled) {
777 public void loadingNodesDone(String directory) {
778 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
784 public void loadingNodesFailed(String directory, Throwable throwable) {
791 public void savingNodesDone(String directory) {
792 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
798 public void savingNodesFailed(String directory, Throwable throwable) {
805 public void coreLoaded() {
806 mainWindow.setVisible(true);
807 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
813 public void coreStopped() {
814 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
820 public void nodeAdded(final Node node) {
821 logger.log(Level.INFO, "node added: " + node);
823 node.addPropertyChangeListener(this);
824 logger.log(Level.FINE, "nodeList.size(): " + nodeList.size());
825 manageNodesDialog.setNodeList(nodeList);
826 nodeConnectActions.put(node, new I18nAction("mainWindow.menu.connect") {
831 @SuppressWarnings("synthetic-access")
832 public void actionPerformed(ActionEvent e) {
836 nodeDisconnectActions.put(node, new I18nAction("mainWindow.menu.disconnect") {
841 @SuppressWarnings("synthetic-access")
842 public void actionPerformed(ActionEvent e) {
843 nodeDisconnect(node);
846 nodeDisconnectActions.get(node).setEnabled(false);
847 mainWindow.addNode(node);
853 public void nodeRemoved(Node node) {
854 logger.log(Level.INFO, "node removed: " + node);
855 nodeList.remove(node);
856 node.removePropertyChangeListener(this);
857 nodeConnectActions.remove(node);
858 nodeDisconnectActions.remove(node);
859 mainWindow.removeNode(node);
865 public void nodeConnecting(Node node) {
866 nodeConnectActions.get(node).setEnabled(false);
867 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
873 public void nodeConnected(Node node) {
874 nodeDisconnectActions.get(node).setEnabled(true);
875 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectedToNode", node.getName(), node.getHostname(), node.getPort()));
881 public void nodeConnectionFailed(Node node, Throwable cause) {
882 nodeConnectActions.get(node).setEnabled(true);
883 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectionToNodeFailed", node.getName(), node.getHostname(), node.getPort(), (cause != null) ? cause.getMessage() : "no reason given"));
884 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);
890 public void nodeDisconnected(Node node, Throwable throwable) {
891 nodeDisconnectActions.get(node).setEnabled(false);
892 nodeConnectActions.get(node).setEnabled(true);
893 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.disconnectedFromNode", node.getName(), node.getHostname(), node.getPort()));
897 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertStarted(net.pterodactylus.jsite.project.Project)
899 public void projectInsertStarted(Project project) {
900 mainWindow.projectInsertStarted(project);
904 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertProgressed(net.pterodactylus.jsite.project.Project,
905 * int, int, int, int, int, boolean)
907 public void projectInsertProgressed(Project project, int totalBlocks, int requiredBlocks, int successfulBlocks, int failedBlocks, int fatallyFailedBlocks, boolean finalizedTotal) {
908 mainWindow.projectInsertProgressed(project, totalBlocks, requiredBlocks, successfulBlocks, failedBlocks, fatallyFailedBlocks, finalizedTotal);
912 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertGeneratedURI(net.pterodactylus.jsite.project.Project,
915 public void projectInsertGeneratedURI(Project project, String uri) {
916 mainWindow.projectInsertGeneratedURI(project);
920 * @see net.pterodactylus.jsite.core.CoreListener#projectInsertFinished(net.pterodactylus.jsite.project.Project,
923 public void projectInsertFinished(Project project, boolean success) {
924 mainWindow.projectInsertFinished(project, success);
928 // INTERFACE LoggingListener
934 public void logged(LogRecord logRecord) {
935 logWindow.logged(logRecord);
939 // INTERFACE PropertyChangeListener
945 public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
946 if (propertyChangeEvent.getSource() instanceof Node) {
947 Node changedNode = (Node) propertyChangeEvent.getSource();
948 nodeConnectActions.get(changedNode).putValue(Action.NAME, changedNode.getName());
949 nodeDisconnectActions.get(changedNode).putValue(Action.NAME, changedNode.getName());