<commit_before>/**
 *                    
 * @author grog (at) myrobotlab.org
 *  
 * This file is part of MyRobotLab (http://myrobotlab.org).
 *
 * MyRobotLab is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License 2.0 as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version (subject to the "Classpath" exception
 * as provided in the LICENSE.txt file that accompanied this code).
 *
 * MyRobotLab is distributed in the hope that it will be useful or fun,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Apache License 2.0 for more details.
 *
 * All libraries in thirdParty bundle are subject to their own license
 * requirements - please refer to http://myrobotlab.org/libraries for 
 * details.
 * 
 * Enjoy !
 * 
 * */

package org.myrobotlab.service;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.myrobotlab.codec.CodecUtils;
import org.myrobotlab.framework.Instantiator;
import org.myrobotlab.framework.Message;
import org.myrobotlab.framework.Platform;
import org.myrobotlab.framework.Registration;
import org.myrobotlab.framework.Service;
import org.myrobotlab.framework.ServiceType;
import org.myrobotlab.framework.Status;
import org.myrobotlab.framework.interfaces.ServiceInterface;
import org.myrobotlab.image.Util;
import org.myrobotlab.logging.Appender;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.service.interfaces.Gateway;
import org.myrobotlab.swing.ServiceGui;
import org.myrobotlab.swing.SwingGuiGui;
import org.myrobotlab.swing.Welcome;
import org.myrobotlab.swing.widget.AboutDialog;
import org.myrobotlab.swing.widget.Console;
import org.myrobotlab.swing.widget.DockableTab;
import org.myrobotlab.swing.widget.DockableTabPane;
import org.myrobotlab.swing.widget.FileUtil;
import org.slf4j.Logger;

import com.mxgraph.model.mxCell;
import com.mxgraph.view.mxGraph;

/**
 * SwingGui - This is the java swing based GUI for MyRobotLab. This service
 * allows other services control features to be displayed. It is the service
 * which you "see" when you start MyRobotLab. It provides a service tab for
 * other services. With its own tab it provides a map of message routes and
 * icons of currently running services.
 * 
 * SwingGui -&gt; Look at service registry SwingGui -&gt; attempt to create a
 * panel for each registered service SwingGui -&gt; create panel SwingGui -&gt;
 * panel.init(this, serviceName); panel.send(Notify, someoutputfn, GUIName,
 * panel.inputfn, data);
 *
 * serviceName (source) --&gt; SwingGui-&gt; msg Arduino arduino01 -&gt; post
 * message -&gt; outbox -&gt; outbound -&gt; notifyList -&gt; reference of
 * sender? (NO) will not transport across process boundry
 * 
 * serviceGUI needs a Runtime Arduino arduin-&gt; post back (data) --&gt;
 * SwingGui - look up serviceGUI by senders name ServiceGUI-&gt;invoke(data)
 * 
 * References :
 * http://www.scribd.com/doc/13122112/Java6-Rules-Adding-Components-To-The-
 * Tabs-On-JTabbedPaneI-Now-A-breeze
 * 
 */
public class SwingGui extends Service implements Gateway, WindowListener, ActionListener, Serializable, DocumentListener {

  private static final long serialVersionUID = 1L;
  transient public final static Logger log = LoggerFactory.getLogger(SwingGui.class);

  String graphXML = "";

  boolean fullscreen;
  public int closeTimeout = 0;

  // TODO - make MTOD !! from internet
  // TODO - spawn thread callback / subscribe / promise - for new version

  transient JFrame frame;
  transient DockableTabPane tabs;// is loaded = new DockableTabPane(this);
  transient SwingGuiGui guiServiceGui;
  transient JPanel tabPanel;
  Map<String, String> userDefinedServiceTypeColors = new HashMap<String, String>();
  /**
   * the all important 2nd stage routing map after the message gets back to the
   * gui service the 'rest' of the callback is handled with this data structure
   * 
   * <pre>
   *     "serviceName.method" --&gt; List<ServiceGui>
   *     Map<{name}.{method}, List<ServiceGui>>> nameMethodCallbackMap
   * 
   * </pre>
   * 
   * The same mechanism is employed in mrl.js to handle all call-backs to
   * ServiceGui.js derived panels.
   * 
   * FIXME / TODO ? - "Probably" should be Map<{name}, Map<{method}, List
   * <ServiceGui>>>
   *
   */

  transient Map<String, List<ServiceGui>> nameMethodCallbackMap = new HashMap<String, List<ServiceGui>>();

  /**
   * normalized set of ServiceGui(s)
   */
  transient Map<String, ServiceGui> serviceGuis = new TreeMap<>();

  transient JTextField status = new JTextField("status:");
  transient JButton statusClear = new JButton("clear");

  transient final JTextField search = new JTextField();

  boolean active = false;

  /**
   * used for "this" reference in anonymous swing utilities calls
   */
  transient SwingGui self;
  transient private JMenu export;
  transient private JMenu import_;
  transient private JMenu refresh;
  private String guiId;

  static public void attachJavaConsole() {
    JFrame j = new JFrame("Java Console");
    j.setSize(500, 550);
    Console c = new Console();
    j.add(c.getScrollPane());
    j.setVisible(true);
    c.startLogging();
  }

  static public void console() {
    attachJavaConsole();
  }

  public static List<Component> getAllComponents(final Container c) {
    Component[] comps = c.getComponents();
    List<Component> compList = new ArrayList<Component>();
    for (Component comp : comps) {
      compList.add(comp);
      if (comp instanceof Container)
        compList.addAll(getAllComponents((Container) comp));
    }
    return compList;
  }

  public void setColor(String serviceType, String hexColor) {
    userDefinedServiceTypeColors.put(serviceType, hexColor);
  }

  public static Color getColorFromURI(Object uri) {
    StringBuffer sb = new StringBuffer(String.format("%d", Math.abs(uri.hashCode())));
    Color c = new Color(Color.HSBtoRGB(Float.parseFloat("0." + sb.reverse().toString()), 0.8f, 0.7f));
    return c;
  }

  public Color getColorHash(String uri) {
    if (userDefinedServiceTypeColors.containsKey(uri)) {
      // e.g. "#FFCCEE"
      return Color.decode(userDefinedServiceTypeColors.get(uri));
    }
    StringBuffer sb = new StringBuffer(String.format("%d", Math.abs(uri.hashCode())));
    Color c = new Color(Color.HSBtoRGB(Float.parseFloat("0." + sb.reverse().toString()), 0.4f, 0.95f));
    return c;
  }

  static public void restart() {
    JFrame frame = new JFrame();
    frame.setLocationByPlatform(true);

    int ret = JOptionPane.showConfirmDialog(frame, "<html>New components have been added,<br>" + " it is necessary to restart in order to use them.</html>", "restart",
        JOptionPane.YES_NO_OPTION);
    if (ret == JOptionPane.OK_OPTION) {
      log.info("restarting");
      // Runtime.restart(restartScript);
      Runtime.getInstance().restart(); // <-- FIXME WRONG need to send
      // message - may be remote !!
    } else {
      log.info("chose not to restart");
      return;
    }
  }

  public SwingGui(String n, String id) {
    super(n, id);
    this.self = this;
    guiId = "gui-" + getId();

    status.setEditable(false);
    if (tabs == null) {
      tabs = new DockableTabPane(this);
    } else {
      tabs.setStateSaver(this);
    }

    // setup the virtual reflection
    try {
      connect("gui://local/messages");
    } catch (Exception e) {
    }

    log.info("tabs size {}", tabs.size());

    for (String title : tabs.keySet()) {
      DockableTab tab = tabs.get(title);
      log.info("{} ({},{}) w={} h={}", title, tab.getX(), tab.getY(), tab.getWidth(), tab.getHeight());
    }
    // subscribe to services being added and removed
    // we want to know about new services registered or released
    // we create explicit mappings vs just [
    // subscribe(Runtime.getRuntimeName(), "released") ]
    // because we would 'mask' the Runtime's service subscriptions -
    // intercept and mask them
    // new service --go--> addTab
    // remove service --go--> removeTab
    subscribe("runtime", "released", getName(), "removeTab");
    // subscribe("runtime", "registered", getName(), "addTab");
    subscribeToRuntime("registered");
  }
  
  public void onRegistered(Registration registration) {
    addTab(registration.service);
  }

  public void about() {
    new AboutDialog(this);
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    String cmd = e.getActionCommand();
    Object o = e.getSource();

    if (statusClear == o) {
      status.setForeground(Color.black);
      status.setOpaque(false);
      status.setText("status:");
    }

    if ("unhide all".equals(cmd)) {
      unhideAll();
    } else if ("hide all".equals(cmd)) {
      hideAll();
    } else if (cmd.equals(Appender.FILE)) {
      Logging logging = LoggingFactory.getInstance();
      logging.addAppender(Appender.FILE);
    } else if (cmd.equals(Appender.NONE)) {
      Logging logging = LoggingFactory.getInstance();
      logging.addAppender(Appender.NONE);
    } else if ("explode".equals(cmd)) {
      explode();
    } else if ("collapse".equals(cmd)) {
      collapse();
    } else if ("about".equals(cmd)) {
      new AboutDialog(this);
      // display();
    } else if ("refresh".equals(cmd)) {
      send(getSelected(), "broadcastState");
    } else if ("refresh all".equals(cmd)) {
      for (String serviceName : Runtime.getServiceNames()) {
        send(serviceName, "broadcastState");
      }
    } else if ("load".equals(cmd)) {
      // get current focus
      String tabName = getSelected();
      ServiceInterface si = Runtime.getService(tabName);
      if (si == null) {
        log.info("%s is not a service - please select the service to save", tabName);
      } else if (si.isLocal()) {
        si.load();
      } else {
        send(tabName, "load");
      }
      info("loaded %s", tabName);
    } else if ("load all".equals(cmd)) {
      String[] services = Runtime.getServiceNames();
      for (String service : services) {
        info("loading %s", service);
        ServiceInterface si = Runtime.getService(service);
        if (si == null) {
          log.info("%s is not a service - please select the service to save", service);
        } else if (si.isLocal()) {
          si.load();
        } else {
          send(service, "load");
        }
      }
      info("loaded all services");
    } else if ("export".equals(cmd)) {
      // get current focus
      String tabName = getSelected();
      ServiceInterface si = Runtime.getService(tabName);
      if (si == null) {
        log.info("%s is not a service - please select the service to save", tabName);
        return;
      }
      // send(tabName, "save"); save has just become serialize
      String newFile = FileUtil.saveAsFileName(getFrame(), String.format("%s.py", tabName));
      try {
        if (newFile != null) {
          export(newFile, newFile);
        }
      } catch (IOException e1) {
        log.error("could not export {} to {}", tabName, newFile);
      }
      info("exported %s", tabName);
    } else if ("export all".equals(cmd)) {

      String newFile = FileUtil.saveAsFileName(getFrame(), "export.py");
      try {
        if (newFile != null) {
          exportAll(newFile);
        }
      } catch (IOException e1) {
        log.error("could not export all to {}", newFile);
      }

      info("saved all exported");
    } else {
      log.info("unknown command {}", cmd);
    }
  }

  /**
   * add a service tab to the SwingGui
   * 
   * @param sw
   *          - name of service to add
   * 
   *          FIXME - full parameter of addTab(final String serviceName, final
   *          String serviceType, final String lable) then overload
   * 
   *          FIXME - this method should be named onRegistered
   */
  synchronized public void addTab(final ServiceInterface sw) {

    if (Runtime.isHeadless()) {
      log.warn("{} SwingGui is in headless environment", getName());
      return;
    }

    // let all current "tabs" handle a new service - possibly by refreshing sets
    // of controllers, encoders or other possible "attachables"
    /// xxx for (ServiceGui serviceGuis)

    // scroll.addItem(sw.getName());

    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {

        String name = sw.getFullName();// sw.getName();

        // change tab color based on name
        // it is better to add a new interfaced method I think ?

        String guiClass = String.format("org.myrobotlab.swing.%sGui", sw.getSimpleName());

        log.debug("createTab {} {}", name, guiClass);
        ServiceGui newGui = null;

        try {
          newGui = (ServiceGui) Instantiator.getThrowableNewInstance(null, guiClass, name, self);
        } catch (Exception e) {
          log.info("could not create a {}", guiClass);
        }

        if (newGui == null) {
          log.info("could not construct a {} object - creating generic template", guiClass);
          newGui = (ServiceGui) Instantiator.getNewInstance("org.myrobotlab.swing.NoGui", name, self);
        }

        newGui.subscribeGui();

        // state and status are both subscribed for the service here
        // these are messages going to the services of interest
        subscribe(name, "publishStatus");
        subscribe(name, "publishState");

        // this is preparing our routing map for callback
        // so when we receive our callback message we know where to route it<commit_msg>short name in tab - will be a problem of collisions in distributed mrl<commit_after>/**
 *                    
 * @author grog (at) myrobotlab.org
 *  
 * This file is part of MyRobotLab (http://myrobotlab.org).
 *
 * MyRobotLab is free software: you can redistribute it and/or modify
 * it under the terms of the Apache License 2.0 as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version (subject to the "Classpath" exception
 * as provided in the LICENSE.txt file that accompanied this code).
 *
 * MyRobotLab is distributed in the hope that it will be useful or fun,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * Apache License 2.0 for more details.
 *
 * All libraries in thirdParty bundle are subject to their own license
 * requirements - please refer to http://myrobotlab.org/libraries for 
 * details.
 * 
 * Enjoy !
 * 
 * */

package org.myrobotlab.service;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

import org.myrobotlab.codec.CodecUtils;
import org.myrobotlab.framework.Instantiator;
import org.myrobotlab.framework.Message;
import org.myrobotlab.framework.Platform;
import org.myrobotlab.framework.Registration;
import org.myrobotlab.framework.Service;
import org.myrobotlab.framework.ServiceType;
import org.myrobotlab.framework.Status;
import org.myrobotlab.framework.interfaces.ServiceInterface;
import org.myrobotlab.image.Util;
import org.myrobotlab.logging.Appender;
import org.myrobotlab.logging.Level;
import org.myrobotlab.logging.LoggerFactory;
import org.myrobotlab.logging.Logging;
import org.myrobotlab.logging.LoggingFactory;
import org.myrobotlab.service.interfaces.Gateway;
import org.myrobotlab.swing.ServiceGui;
import org.myrobotlab.swing.SwingGuiGui;
import org.myrobotlab.swing.Welcome;
import org.myrobotlab.swing.widget.AboutDialog;
import org.myrobotlab.swing.widget.Console;
import org.myrobotlab.swing.widget.DockableTab;
import org.myrobotlab.swing.widget.DockableTabPane;
import org.myrobotlab.swing.widget.FileUtil;
import org.slf4j.Logger;

import com.mxgraph.model.mxCell;
import com.mxgraph.view.mxGraph;

/**
 * SwingGui - This is the java swing based GUI for MyRobotLab. This service
 * allows other services control features to be displayed. It is the service
 * which you "see" when you start MyRobotLab. It provides a service tab for
 * other services. With its own tab it provides a map of message routes and
 * icons of currently running services.
 * 
 * SwingGui -&gt; Look at service registry SwingGui -&gt; attempt to create a
 * panel for each registered service SwingGui -&gt; create panel SwingGui -&gt;
 * panel.init(this, serviceName); panel.send(Notify, someoutputfn, GUIName,
 * panel.inputfn, data);
 *
 * serviceName (source) --&gt; SwingGui-&gt; msg Arduino arduino01 -&gt; post
 * message -&gt; outbox -&gt; outbound -&gt; notifyList -&gt; reference of
 * sender? (NO) will not transport across process boundry
 * 
 * serviceGUI needs a Runtime Arduino arduin-&gt; post back (data) --&gt;
 * SwingGui - look up serviceGUI by senders name ServiceGUI-&gt;invoke(data)
 * 
 * References :
 * http://www.scribd.com/doc/13122112/Java6-Rules-Adding-Components-To-The-
 * Tabs-On-JTabbedPaneI-Now-A-breeze
 * 
 */
public class SwingGui extends Service implements Gateway, WindowListener, ActionListener, Serializable, DocumentListener {

  private static final long serialVersionUID = 1L;
  transient public final static Logger log = LoggerFactory.getLogger(SwingGui.class);

  String graphXML = "";

  boolean fullscreen;
  public int closeTimeout = 0;

  // TODO - make MTOD !! from internet
  // TODO - spawn thread callback / subscribe / promise - for new version

  transient JFrame frame;
  transient DockableTabPane tabs;// is loaded = new DockableTabPane(this);
  transient SwingGuiGui guiServiceGui;
  transient JPanel tabPanel;
  Map<String, String> userDefinedServiceTypeColors = new HashMap<String, String>();
  /**
   * the all important 2nd stage routing map after the message gets back to the
   * gui service the 'rest' of the callback is handled with this data structure
   * 
   * <pre>
   *     "serviceName.method" --&gt; List<ServiceGui>
   *     Map<{name}.{method}, List<ServiceGui>>> nameMethodCallbackMap
   * 
   * </pre>
   * 
   * The same mechanism is employed in mrl.js to handle all call-backs to
   * ServiceGui.js derived panels.
   * 
   * FIXME / TODO ? - "Probably" should be Map<{name}, Map<{method}, List
   * <ServiceGui>>>
   *
   */

  transient Map<String, List<ServiceGui>> nameMethodCallbackMap = new HashMap<String, List<ServiceGui>>();

  /**
   * normalized set of ServiceGui(s)
   */
  transient Map<String, ServiceGui> serviceGuis = new TreeMap<>();

  transient JTextField status = new JTextField("status:");
  transient JButton statusClear = new JButton("clear");

  transient final JTextField search = new JTextField();

  boolean active = false;

  /**
   * used for "this" reference in anonymous swing utilities calls
   */
  transient SwingGui self;
  transient private JMenu export;
  transient private JMenu import_;
  transient private JMenu refresh;
  private String guiId;

  static public void attachJavaConsole() {
    JFrame j = new JFrame("Java Console");
    j.setSize(500, 550);
    Console c = new Console();
    j.add(c.getScrollPane());
    j.setVisible(true);
    c.startLogging();
  }

  static public void console() {
    attachJavaConsole();
  }

  public static List<Component> getAllComponents(final Container c) {
    Component[] comps = c.getComponents();
    List<Component> compList = new ArrayList<Component>();
    for (Component comp : comps) {
      compList.add(comp);
      if (comp instanceof Container)
        compList.addAll(getAllComponents((Container) comp));
    }
    return compList;
  }

  public void setColor(String serviceType, String hexColor) {
    userDefinedServiceTypeColors.put(serviceType, hexColor);
  }

  public static Color getColorFromURI(Object uri) {
    StringBuffer sb = new StringBuffer(String.format("%d", Math.abs(uri.hashCode())));
    Color c = new Color(Color.HSBtoRGB(Float.parseFloat("0." + sb.reverse().toString()), 0.8f, 0.7f));
    return c;
  }

  public Color getColorHash(String uri) {
    if (userDefinedServiceTypeColors.containsKey(uri)) {
      // e.g. "#FFCCEE"
      return Color.decode(userDefinedServiceTypeColors.get(uri));
    }
    StringBuffer sb = new StringBuffer(String.format("%d", Math.abs(uri.hashCode())));
    Color c = new Color(Color.HSBtoRGB(Float.parseFloat("0." + sb.reverse().toString()), 0.4f, 0.95f));
    return c;
  }

  static public void restart() {
    JFrame frame = new JFrame();
    frame.setLocationByPlatform(true);

    int ret = JOptionPane.showConfirmDialog(frame, "<html>New components have been added,<br>" + " it is necessary to restart in order to use them.</html>", "restart",
        JOptionPane.YES_NO_OPTION);
    if (ret == JOptionPane.OK_OPTION) {
      log.info("restarting");
      // Runtime.restart(restartScript);
      Runtime.getInstance().restart(); // <-- FIXME WRONG need to send
      // message - may be remote !!
    } else {
      log.info("chose not to restart");
      return;
    }
  }

  public SwingGui(String n, String id) {
    super(n, id);
    this.self = this;
    guiId = "gui-" + getId();

    status.setEditable(false);
    if (tabs == null) {
      tabs = new DockableTabPane(this);
    } else {
      tabs.setStateSaver(this);
    }

    // setup the virtual reflection
    try {
      connect("gui://local/messages");
    } catch (Exception e) {
    }

    log.info("tabs size {}", tabs.size());

    for (String title : tabs.keySet()) {
      DockableTab tab = tabs.get(title);
      log.info("{} ({},{}) w={} h={}", title, tab.getX(), tab.getY(), tab.getWidth(), tab.getHeight());
    }
    // subscribe to services being added and removed
    // we want to know about new services registered or released
    // we create explicit mappings vs just [
    // subscribe(Runtime.getRuntimeName(), "released") ]
    // because we would 'mask' the Runtime's service subscriptions -
    // intercept and mask them
    // new service --go--> addTab
    // remove service --go--> removeTab
    subscribe("runtime", "released", getName(), "removeTab");
    // subscribe("runtime", "registered", getName(), "addTab");
    subscribeToRuntime("registered");
  }
  
  public void onRegistered(Registration registration) {
    addTab(registration.service);
  }

  public void about() {
    new AboutDialog(this);
  }

  @Override
  public void actionPerformed(ActionEvent e) {
    String cmd = e.getActionCommand();
    Object o = e.getSource();

    if (statusClear == o) {
      status.setForeground(Color.black);
      status.setOpaque(false);
      status.setText("status:");
    }

    if ("unhide all".equals(cmd)) {
      unhideAll();
    } else if ("hide all".equals(cmd)) {
      hideAll();
    } else if (cmd.equals(Appender.FILE)) {
      Logging logging = LoggingFactory.getInstance();
      logging.addAppender(Appender.FILE);
    } else if (cmd.equals(Appender.NONE)) {
      Logging logging = LoggingFactory.getInstance();
      logging.addAppender(Appender.NONE);
    } else if ("explode".equals(cmd)) {
      explode();
    } else if ("collapse".equals(cmd)) {
      collapse();
    } else if ("about".equals(cmd)) {
      new AboutDialog(this);
      // display();
    } else if ("refresh".equals(cmd)) {
      send(getSelected(), "broadcastState");
    } else if ("refresh all".equals(cmd)) {
      for (String serviceName : Runtime.getServiceNames()) {
        send(serviceName, "broadcastState");
      }
    } else if ("load".equals(cmd)) {
      // get current focus
      String tabName = getSelected();
      ServiceInterface si = Runtime.getService(tabName);
      if (si == null) {
        log.info("%s is not a service - please select the service to save", tabName);
      } else if (si.isLocal()) {
        si.load();
      } else {
        send(tabName, "load");
      }
      info("loaded %s", tabName);
    } else if ("load all".equals(cmd)) {
      String[] services = Runtime.getServiceNames();
      for (String service : services) {
        info("loading %s", service);
        ServiceInterface si = Runtime.getService(service);
        if (si == null) {
          log.info("%s is not a service - please select the service to save", service);
        } else if (si.isLocal()) {
          si.load();
        } else {
          send(service, "load");
        }
      }
      info("loaded all services");
    } else if ("export".equals(cmd)) {
      // get current focus
      String tabName = getSelected();
      ServiceInterface si = Runtime.getService(tabName);
      if (si == null) {
        log.info("%s is not a service - please select the service to save", tabName);
        return;
      }
      // send(tabName, "save"); save has just become serialize
      String newFile = FileUtil.saveAsFileName(getFrame(), String.format("%s.py", tabName));
      try {
        if (newFile != null) {
          export(newFile, newFile);
        }
      } catch (IOException e1) {
        log.error("could not export {} to {}", tabName, newFile);
      }
      info("exported %s", tabName);
    } else if ("export all".equals(cmd)) {

      String newFile = FileUtil.saveAsFileName(getFrame(), "export.py");
      try {
        if (newFile != null) {
          exportAll(newFile);
        }
      } catch (IOException e1) {
        log.error("could not export all to {}", newFile);
      }

      info("saved all exported");
    } else {
      log.info("unknown command {}", cmd);
    }
  }

  /**
   * add a service tab to the SwingGui
   * 
   * @param sw
   *          - name of service to add
   * 
   *          FIXME - full parameter of addTab(final String serviceName, final
   *          String serviceType, final String lable) then overload
   * 
   *          FIXME - this method should be named onRegistered
   */
  synchronized public void addTab(final ServiceInterface sw) {

    if (Runtime.isHeadless()) {
      log.warn("{} SwingGui is in headless environment", getName());
      return;
    }

    // let all current "tabs" handle a new service - possibly by refreshing sets
    // of controllers, encoders or other possible "attachables"
    /// xxx for (ServiceGui serviceGuis)

    // scroll.addItem(sw.getName());

    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {

    	// FIXME - this will be an issue of name collision in distributed mrl !!!
        String name = sw.getName();// sw.getFullName();// sw.getName();

        // change tab color based on name
        // it is better to add a new interfaced method I think ?

        String guiClass = String.format("org.myrobotlab.swing.%sGui", sw.getSimpleName());

        log.debug("createTab {} {}", name, guiClass);
        ServiceGui newGui = null;

        try {
          newGui = (ServiceGui) Instantiator.getThrowableNewInstance(null, guiClass, name, self);
        } catch (Exception e) {
          log.info("could not create a {}", guiClass);
        }

        if (newGui == null) {
          log.info("could not construct a {} object - creating generic template", guiClass);
          newGui = (ServiceGui) Instantiator.getNewInstance("org.myrobotlab.swing.NoGui", name, self);
        }

        newGui.subscribeGui();

        // state and status are both subscribed for the service here
        // these are messages going to the services of interest
        subscribe(name, "publishStatus");
        subscribe(name, "publishState");

        // this is preparing our routing map for callback
        // so when we receive our callback message we know where to route it
{'fraction_non_alphanumeric': 0.12020870276906288, 'fraction_numerical': 0.0026599372527622424, 'mean_word_length': 3.1466346153846154, 'pattern_counts': {'":': 0, '<': 49, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '129732', 'n_tokens_mistral': 8837, 'n_tokens_neox': 8606, 'n_words': 2532}