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;
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.HashMap;
30 import java.util.List;
31 import java.util.Locale;
33 import java.util.Properties;
34 import java.util.concurrent.Executor;
35 import java.util.concurrent.Executors;
36 import java.util.logging.Level;
37 import java.util.logging.LogRecord;
38 import java.util.logging.Logger;
40 import javax.swing.AbstractAction;
41 import javax.swing.Action;
42 import javax.swing.JOptionPane;
43 import javax.swing.UIManager;
44 import javax.swing.UnsupportedLookAndFeelException;
46 import net.pterodactylus.jsite.core.Core;
47 import net.pterodactylus.jsite.core.CoreListener;
48 import net.pterodactylus.jsite.core.Node;
49 import net.pterodactylus.jsite.core.Project;
50 import net.pterodactylus.jsite.core.Request;
51 import net.pterodactylus.jsite.i18n.I18n;
52 import net.pterodactylus.jsite.i18n.gui.I18nAction;
53 import net.pterodactylus.util.image.IconLoader;
54 import net.pterodactylus.util.io.Closer;
55 import net.pterodactylus.util.logging.Logging;
56 import net.pterodactylus.util.logging.LoggingListener;
59 * The Swing user interface.
61 * @author David ‘Bombe’ Roden <bombe@freenetproject.org>
64 public class SwingInterface implements CoreListener, LoggingListener {
67 private static final Logger logger = Logging.getLogger(SwingInterface.class.getName());
69 /** The application core. */
70 private final Core core;
72 /** The configuration directory. */
73 private final String configDirectory;
75 /** The main window. */
76 private MainWindow mainWindow;
79 private Executor threadPool = Executors.newCachedThreadPool();
81 /** The logger window. */
82 private LogWindow logWindow;
84 /** The “configure” action. */
85 private I18nAction configureAction;
87 /** The “import config” action. */
88 private I18nAction importConfigAction;
90 /** The “quit” action. */
91 private I18nAction quitAction;
93 /** The “manage nodes” action. */
94 private I18nAction manageNodesAction;
96 /** The “connect to node” (simple mode) action. */
97 private I18nAction nodeConnectAction;
99 /** The “disconnect from node” (simple mode) action. */
100 private I18nAction nodeDisconnectAction;
102 /** All node menu items. */
103 private List<Action> nodeConnectActions = Collections.synchronizedList(new ArrayList<Action>());
105 /** Mapping from nodes to node connect actions. */
106 private Map<Node, Action> nodeNodeConnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
108 /** Mapping from node connect actions to nodes. */
109 private Map<Action, Node> nodeConnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
111 /** All node disconnect actions. */
112 private List<Action> nodeDisconnectActions = Collections.synchronizedList(new ArrayList<Action>());
114 /** Mapping from nodes to node disconnect actions. */
115 private Map<Node, Action> nodeNodeDisconnectActions = Collections.synchronizedMap(new HashMap<Node, Action>());
117 /** Mapping from node disconnect actions to nodes. */
118 private Map<Action, Node> nodeDisconnectActionNodes = Collections.synchronizedMap(new HashMap<Action, Node>());
120 /** The node manager dialog. */
121 private ManageNodesDialog manageNodesDialog;
123 /** All lanugage menu items. */
124 private List<I18nAction> languageActions = new ArrayList<I18nAction>();
126 /** The “about” action. */
127 private I18nAction helpAboutAction;
129 /** The “add project” action. */
130 private I18nAction addProjectAction;
132 /** The “clone project” action. */
133 private I18nAction cloneProjectAction;
135 /** The “delete project” action. */
136 private I18nAction deleteProjectAction;
138 /** The “about” dialog. */
139 private AboutDialog aboutDialog;
141 /** The configuration dialog. */
142 private ConfigurationDialog configurationDialog;
144 /** The list of all defined nodes. */
145 private List<Node> nodeList = Collections.synchronizedList(new ArrayList<Node>());
151 /** The advanced mode. */
152 private boolean advancedMode;
154 /** Whether to antialias the GUI. */
155 private boolean antialias;
157 /** The control font. */
158 private String controlFont;
160 /** The user font. */
161 private String userFont;
163 /** The class name of the look and feel. */
164 private String lookAndFeel;
166 /** X coordinate of the main window. */
167 private int mainWindowX = -1;
169 /** Y coordinate of the main window. */
170 private int mainWindowY = -1;
172 /** Width of the main window. */
173 private int mainWindowWidth = -1;
175 /** Height of the main window. */
176 private int mainWindowHeight = -1;
179 * Creates a new swing interface.
182 * The core to operate on
183 * @param configDirectory
184 * The directory the configuration is stored in
186 public SwingInterface(Core core, String configDirectory) {
188 this.configDirectory = configDirectory;
189 I18n.setLocale(Locale.ENGLISH);
191 if (lookAndFeel != null) {
193 UIManager.setLookAndFeel(lookAndFeel);
194 } catch (ClassNotFoundException cnfe1) {
195 logger.log(Level.WARNING, "could not load look and feel", cnfe1);
196 } catch (InstantiationException ie1) {
197 logger.log(Level.WARNING, "could not load look and feel", ie1);
198 } catch (IllegalAccessException iae1) {
199 logger.log(Level.WARNING, "could not load look and feel", iae1);
200 } catch (UnsupportedLookAndFeelException ulafe1) {
201 logger.log(Level.WARNING, "could not load look and feel", ulafe1);
205 System.setProperty("swing.aatext", "true");
207 if (controlFont != null) {
208 System.setProperty("swing.plaf.metal.controlFont", controlFont);
210 if (userFont != null) {
211 System.setProperty("swing.plaf.metal.userFont", userFont);
215 mainWindow = new MainWindow(this);
216 mainWindow.setAdvancedMode(advancedMode);
217 if ((mainWindowX != -1) && (mainWindowY != -1) && (mainWindowWidth != -1) && (mainWindowHeight != -1)) {
218 mainWindow.setLocation(mainWindowX, mainWindowY);
219 mainWindow.setSize(mainWindowWidth, mainWindowHeight);
221 logWindow = new LogWindow();
229 * Returns the core that is controlled by the Swing interface.
238 * Returns the main window of the Swing interface.
240 * @return The main window
242 MainWindow getMainWindow() {
247 * Returns whether the advanced mode is activated.
249 * @return <code>true</code> if the advanced mode is activated,
250 * <code>false</code> if the simple mode is activated
252 boolean isAdvancedMode() {
257 * Returns the “configure” action.
259 * @return The “configure” action
261 I18nAction getConfigureAction() {
262 return configureAction;
266 * Returns the “import config” action.
268 * @return The “import config” action
270 I18nAction getImportConfigAction() {
271 return importConfigAction;
275 * Returns the “quit” action.
277 * @return The “quit” action
279 I18nAction getQuitAction() {
284 * Returns the “manage nodes” action.
286 * @return The “manage nodes” action
288 I18nAction getManageNodesAction() {
289 return manageNodesAction;
293 * Returns the “connect to node” action.
295 * @return The “connect to node” action
297 I18nAction getNodeConnectAction() {
298 return nodeConnectAction;
302 * Returns all “connect node” actions.
304 * @return All “connect node” actions
306 List<Action> getNodeConnectActions() {
307 return nodeConnectActions;
311 * Returns the “disconnect from node” action.
313 * @return The “disconnect from node” action
315 I18nAction getNodeDisconnectAction() {
316 return nodeDisconnectAction;
320 * Returns all “disconnect node” actions.
322 * @return All “disconnect node” action
324 List<Action> getNodeDisconnectActions() {
325 return nodeDisconnectActions;
329 * Returns all language actions.
331 * @return All language actions
333 List<I18nAction> getLanguageActions() {
334 return languageActions;
338 * Returns the “about” action.
340 * @return The “about” action
342 I18nAction getHelpAboutAction() {
343 return helpAboutAction;
347 * Returns the “add project” action.
349 * @return The “add project” action
351 I18nAction getAddProjectAction() {
352 return addProjectAction;
356 * Returns the “clone project” action.
358 * @return The “clone project” action
360 I18nAction getCloneProjectAction() {
361 return cloneProjectAction;
365 * Returns the “delete project” action.
367 * @return The “delete project” action
369 I18nAction getDeleteProjectAction() {
370 return deleteProjectAction;
386 * Loads the configuration of the interface.
388 private void loadConfig() {
389 /* initialize default stuff. */
391 /* now read config. */
392 File configFile = new File(configDirectory, "swing-interface.properties");
393 if (!configFile.exists() || !configFile.canRead() || !configFile.isFile()) {
394 System.err.println("could not find “" + configFile.getAbsolutePath() + "”!");
397 Properties configProperties = new Properties();
398 FileInputStream configInputStream = null;
400 configInputStream = new FileInputStream(configFile);
401 configProperties.load(configInputStream);
402 } catch (IOException ioe1) {
403 System.err.println("could not load config, " + ioe1.getMessage());
405 Closer.close(configInputStream);
407 if (configProperties.containsKey("advancedMode")) {
408 advancedMode = Boolean.valueOf(configProperties.getProperty("advancedMode"));
410 if (configProperties.containsKey("antialias")) {
411 antialias = Boolean.valueOf(configProperties.getProperty("antialias"));
413 if (configProperties.containsKey("controlFont")) {
414 controlFont = configProperties.getProperty("controlFont");
416 if (configProperties.containsKey("userFont")) {
417 userFont = configProperties.getProperty("userFont");
419 if (configProperties.containsKey("lookAndFeel")) {
420 lookAndFeel = configProperties.getProperty("lookAndFeel");
422 if (configProperties.containsKey("language")) {
423 I18n.setLocale(new Locale(configProperties.getProperty("language")));
425 if (configProperties.containsKey("mainWindowX")) {
426 mainWindowX = Integer.valueOf(configProperties.getProperty("mainWindowX"));
428 if (configProperties.containsKey("mainWindowY")) {
429 mainWindowY = Integer.valueOf(configProperties.getProperty("mainWindowY"));
431 if (configProperties.containsKey("mainWindowWidth")) {
432 mainWindowWidth = Integer.valueOf(configProperties.getProperty("mainWindowWidth"));
434 if (configProperties.containsKey("mainWindowHeight")) {
435 mainWindowHeight = Integer.valueOf(configProperties.getProperty("mainWindowHeight"));
440 * Saves the configuration.
442 private void saveConfig() {
443 File configDirectory = new File(this.configDirectory);
444 if (!configDirectory.exists()) {
445 if (!configDirectory.mkdirs()) {
446 System.err.println("could not create “" + this.configDirectory + "”!");
450 if (!configDirectory.exists() || !configDirectory.isDirectory() || !configDirectory.canWrite()) {
451 System.err.println("can not access “" + this.configDirectory + "”!");
454 File configFile = new File(configDirectory, "swing-interface.properties");
455 Properties configProperties = new Properties();
456 configProperties.setProperty("advancedMode", String.valueOf(advancedMode));
457 configProperties.setProperty("antialias", String.valueOf(antialias));
458 if (controlFont != null) {
459 configProperties.setProperty("controlFont", controlFont);
461 if (userFont != null) {
462 configProperties.setProperty("userFont", userFont);
464 if (lookAndFeel != null) {
465 configProperties.setProperty("lookAndFeel", lookAndFeel);
467 configProperties.setProperty("language", I18n.getLocale().getLanguage());
468 configProperties.setProperty("mainWindowX", String.valueOf(mainWindowX));
469 configProperties.setProperty("mainWindowY", String.valueOf(mainWindowY));
470 configProperties.setProperty("mainWindowWidth", String.valueOf(mainWindowWidth));
471 configProperties.setProperty("mainWindowHeight", String.valueOf(mainWindowHeight));
472 FileOutputStream configOutputStream = null;
474 configOutputStream = new FileOutputStream(configFile);
475 configProperties.store(configOutputStream, "configuration of swing interface");
476 } catch (IOException ioe1) {
477 System.err.println("could not save config, " + ioe1.getMessage());
479 Closer.close(configOutputStream);
484 * Initializes all actions.
486 private void initActions() {
487 configureAction = new I18nAction("mainWindow.menu.jSite.configure", IconLoader.loadIcon("/preferences-system.png")) {
492 @SuppressWarnings("synthetic-access")
493 public void actionPerformed(ActionEvent actionEvent) {
497 importConfigAction = new I18nAction("mainWindow.menu.jSite.importConfig") {
502 @SuppressWarnings("synthetic-access")
503 public void actionPerformed(ActionEvent actionEvent) {
507 quitAction = new I18nAction("mainWindow.menu.jSite.quit", IconLoader.loadIcon("/system-log-out.png")) {
512 @SuppressWarnings("synthetic-access")
513 public void actionPerformed(ActionEvent actionEvent) {
517 manageNodesAction = new I18nAction("mainWindow.menu.node.item.manageNodes") {
522 @SuppressWarnings("synthetic-access")
523 public void actionPerformed(ActionEvent actionEvent) {
527 nodeConnectAction = new I18nAction("mainWindow.menu.node.item.connect", false) {
529 @SuppressWarnings("synthetic-access")
530 public void actionPerformed(ActionEvent actionEvent) {
531 List<Node> nodes = core.getNodes();
532 if (nodes.isEmpty()) {
535 nodeConnect(nodes.get(0));
539 nodeDisconnectAction = new I18nAction("mainWindow.menu.node.item.disconnect", false) {
544 @SuppressWarnings("synthetic-access")
545 public void actionPerformed(ActionEvent e) {
546 List<Node> nodes = core.getNodes();
547 if (nodes.isEmpty()) {
550 nodeDisconnect(nodes.get(0));
553 rebuildNodeActions(core.getNodes());
554 List<Locale> availableLanguages = I18n.findAvailableLanguages();
555 for (final Locale locale: availableLanguages) {
556 I18nAction languageAction = new I18nAction("general.language." + locale.getLanguage()) {
558 @SuppressWarnings("synthetic-access")
559 public void actionPerformed(ActionEvent e) {
560 changeLanguage(locale, this);
564 if (I18n.getLocale().getLanguage().equals(locale.getLanguage())) {
565 languageAction.setEnabled(false);
567 languageActions.add(languageAction);
569 helpAboutAction = new I18nAction("mainWindow.menu.help.item.about") {
574 @SuppressWarnings("synthetic-access")
575 public void actionPerformed(ActionEvent actionEvent) {
579 addProjectAction = new I18nAction("mainWindow.button.addProject") {
584 @SuppressWarnings("synthetic-access")
585 public void actionPerformed(ActionEvent actionEvent) {
589 cloneProjectAction = new I18nAction("mainWindow.button.cloneProject") {
594 @SuppressWarnings("synthetic-access")
595 public void actionPerformed(ActionEvent actionEvent) {
599 deleteProjectAction = new I18nAction("mainWindow.button.deleteProject") {
604 @SuppressWarnings("synthetic-access")
605 public void actionPerformed(ActionEvent actionEvent) {
612 * Initializes all child dialogs.
614 private void initDialogs() {
615 manageNodesDialog = new ManageNodesDialog(this);
616 aboutDialog = new AboutDialog(this);
617 configurationDialog = new ConfigurationDialog(this);
625 * Shows the configuration dialog.
627 private void configure() {
628 configurationDialog.setAdvancedMode(advancedMode);
629 configurationDialog.setAntialias(antialias);
630 configurationDialog.setControlFont(controlFont);
631 configurationDialog.setUserFont(userFont);
632 configurationDialog.setLookAndFeel(lookAndFeel);
633 configurationDialog.setVisible(true);
634 if (!configurationDialog.wasCancelled()) {
635 advancedMode = configurationDialog.isAdvancedMode();
636 if (!advancedMode && (nodeList.size() > 1)) {
637 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.message"), I18n.get("mainWindow.warning.multipleNodesNotAdvancedMode.title"), JOptionPane.WARNING_MESSAGE);
639 mainWindow.setAdvancedMode(advancedMode);
640 antialias = configurationDialog.isAntialias();
641 controlFont = configurationDialog.getControlFont();
642 userFont = configurationDialog.getUserFont();
643 lookAndFeel = configurationDialog.getLookAndFeel();
649 * Imports old jSite configuration.
651 private void importConfig() {
658 private void quit() {
661 mainWindowX = mainWindow.getX();
662 mainWindowY = mainWindow.getY();
663 mainWindowWidth = mainWindow.getWidth();
664 mainWindowHeight = mainWindow.getHeight();
670 * Rebuilds all node connect and disconnect actions.
675 private void rebuildNodeActions(List<Node> nodes) {
676 logger.fine("rebuilding node actions…");
677 nodeConnectActions.clear();
678 nodeNodeConnectActions.clear();
679 nodeConnectActionNodes.clear();
680 nodeDisconnectActions.clear();
681 nodeNodeDisconnectActions.clear();
682 nodeDisconnectActionNodes.clear();
683 for (Node node: nodes) {
684 logger.finer("adding node “" + node + "” to menu");
685 Action nodeConnectAction = new AbstractAction(node.getName()) {
690 @SuppressWarnings("synthetic-access")
691 public void actionPerformed(ActionEvent e) {
692 Node node = nodeConnectActionNodes.get(this);
696 nodeConnectActions.add(nodeConnectAction);
697 nodeConnectActionNodes.put(nodeConnectAction, node);
698 nodeNodeConnectActions.put(node, nodeConnectAction);
699 Action nodeDisconnectAction = new AbstractAction(node.getName()) {
704 @SuppressWarnings("synthetic-access")
705 public void actionPerformed(ActionEvent e) {
706 Node node = nodeDisconnectActionNodes.get(this);
707 nodeDisconnect(node);
710 // nodeDisconnectActions.add(nodeDisconnectAction);
711 nodeDisconnectActionNodes.put(nodeDisconnectAction, node);
712 nodeNodeDisconnectActions.put(node, nodeDisconnectAction);
717 * Pops up the “manage nodes” dialog.
719 private void manageNodes() {
721 manageNodesDialog.setNodeList(nodeList);
722 manageNodesDialog.setVisible(true);
723 nodeList = manageNodesDialog.getNodeList();
724 rebuildNodeActions(nodeList);
725 mainWindow.refreshNodeMenuItems();
727 if (nodeList.isEmpty()) {
728 Node newNode = new Node();
729 newNode.setName(I18n.get("general.defaultNode.name"));
730 newNode.setHostname("localhost");
731 newNode.setPort(9481);
732 nodeList.add(newNode);
734 Node firstNode = nodeList.get(0);
735 EditNodeDialog editNodeDialog = manageNodesDialog.getEditNodeDialog();
736 editNodeDialog.setNodeName(firstNode.getName());
737 editNodeDialog.setNodeHostname(firstNode.getHostname());
738 editNodeDialog.setNodePort(firstNode.getPort());
739 editNodeDialog.setVisible(true);
740 if (!editNodeDialog.wasCancelled()) {
741 firstNode.setName(editNodeDialog.getNodeName());
742 firstNode.setHostname(editNodeDialog.getNodeHostname());
743 firstNode.setPort(editNodeDialog.getNodePort());
744 /* TODO - give to core. */
750 * Connects to the node.
753 * The node to connect to
755 private void nodeConnect(final Node node) {
756 threadPool.execute(new Runnable() {
761 @SuppressWarnings("synthetic-access")
763 logger.log(Level.INFO, "connecting to node “" + node.getName() + "”…");
764 core.connectToNode(node);
770 * Disconnects from the node.
773 * The node to disconnect from
775 private void nodeDisconnect(Node node) {
776 logger.log(Level.INFO, "disconnecting from node “" + node.getName() + "”…");
777 core.disconnectFromNode(node);
781 * Changes the language of the interface. This method also disables the
782 * action for the newly set language and enables all others.
786 * @param languageAction
787 * The action that triggered the change
789 private void changeLanguage(Locale newLocale, I18nAction languageAction) {
790 for (I18nAction i18nAction: languageActions) {
791 i18nAction.setEnabled(i18nAction != languageAction);
793 I18n.setLocale(newLocale);
797 * Shows the “about” dialog.
799 private void helpAbout() {
800 aboutDialog.setVisible(true);
806 private void addProject() {
807 Project project = new Project();
808 project.setName("New Project");
809 project.setDescription("");
815 private void cloneProject() {
822 private void deleteProject() {
827 // INTERFACE CoreListener
833 public void loadingProjectsDone(String directory) {
834 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectLoadingDone"));
840 public void loadingProjectsFailed(String directory, Throwable throwable) {
841 JOptionPane.showMessageDialog(mainWindow, I18n.get("mainWindow.error.projectLoadingFailed.message", directory), I18n.get("mainWindow.error.projectLoadingFailed.title"), JOptionPane.ERROR_MESSAGE);
847 public void savingProjectsDone(String directory) {
848 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.projectSavingDone"));
854 public void savingProjectsFailed(String directory, Throwable throwabled) {
861 public void loadingNodesDone(String directory) {
862 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.loadingNodesDone"));
868 public void loadingNodesFailed(String directory, Throwable throwable) {
875 public void savingNodesDone(String directory) {
876 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.savingNodesDone"));
882 public void savingNodesFailed(String directory, Throwable throwable) {
889 public void coreLoaded() {
890 mainWindow.setVisible(true);
891 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreLoaded"));
897 public void coreStopped() {
898 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.coreStopped"));
904 public void nodeAdded(Node node) {
905 logger.log(Level.INFO, "node added: " + node);
907 manageNodesDialog.setNodeList(nodeList);
908 rebuildNodeActions(nodeList);
909 mainWindow.refreshNodeMenuItems();
915 public void nodeRemoved(Node node) {
916 logger.log(Level.INFO, "node removed: " + node);
917 nodeList.remove(node);
918 rebuildNodeActions(nodeList);
919 mainWindow.refreshNodeMenuItems();
925 public void nodeConnecting(Node node) {
926 Action nodeConnectAction = nodeNodeConnectActions.get(node);
927 nodeConnectActions.remove(nodeConnectAction);
928 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectingToNode", node.getName(), node.getHostname(), node.getPort()));
929 mainWindow.refreshNodeMenuItems();
935 public void nodeConnected(Node node) {
936 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
937 nodeDisconnectActions.add(nodeDisconnectAction);
938 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.connectedToNode", node.getName(), node.getHostname(), node.getPort()));
939 mainWindow.refreshNodeMenuItems();
945 public void nodeDisconnected(Node node, Throwable throwable) {
946 Action nodeConnectAction = nodeNodeConnectActions.get(node);
947 nodeConnectActions.add(nodeConnectAction);
948 Action nodeDisconnectAction = nodeNodeDisconnectActions.get(node);
949 nodeDisconnectActions.remove(nodeDisconnectAction);
950 mainWindow.setStatusBarText(I18n.get("mainWindow.statusBar.disconnectedFromNode", node.getName(), node.getHostname(), node.getPort()));
951 mainWindow.refreshNodeMenuItems();
957 public void requestAdded(Node node, Request request) {
958 logger.log(Level.INFO, "request added to node: " + request + ", " + node);
959 /* TODO - implement */
965 public void requestProgressed(Request request, int totalBlocks, int requiredBlocks, int successfulBlocks, int failedBlocks, int fatallyFailedBlocks, boolean finalizedTotal) {
966 /* TODO - update table model */
970 // INTERFACE LoggingListener
976 public void logged(LogRecord logRecord) {
977 logWindow.logged(logRecord);