/** * * @author greg (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 GNU General Public License 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 * GNU General Public License 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.Serializable; import java.net.URISyntaxException; import java.net.URL; 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.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTabbedPane; import javax.swing.SwingUtilities; import org.myrobotlab.control.GUIServiceGUI; import org.myrobotlab.control.RuntimeGUI; import org.myrobotlab.control.ServiceGUI; import org.myrobotlab.control.TabControl2; import org.myrobotlab.control.Welcome; import org.myrobotlab.control.widget.AboutDialog; import org.myrobotlab.control.widget.Console; import org.myrobotlab.framework.Instantiator; import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.framework.Status; 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.ServiceInterface; import org.slf4j.Logger; import com.mxgraph.model.mxCell; import com.mxgraph.view.mxGraph; /** * GUIService - 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. */ public class GUIService extends Service implements WindowListener, ActionListener, Serializable { /* * GUIService -> Look at service registry GUIService -> attempt to create a * panel for each registered service GUIService -> create panel GUIService -> * panel.init(this, serviceName); panel.send(Notify, someoutputfn, GUIName, * panel.inputfn, data); * * * * serviceName (source) --> GUIService-> msg Arduino arduino01 -> post message * -> outbox -> outbound -> notifyList -> reference of sender? (NO) will not * transport across process boundry * * serviceGUI needs a Runtime Arduino arduin-> post back (data) --> GUIService * - look up serviceGUI by senders name ServiceGUI->invoke(data) * * References : * http://www.scribd.com/doc/13122112/Java6-Rules-Adding-Components-To-The- * Tabs-On-JTabbedPaneI-Now-A-breeze */ private static final long serialVersionUID = 1L; transient public final static Logger log = LoggerFactory.getLogger(GUIService.class); public String graphXML = ""; public transient JFrame frame = null; public String lastTabVisited; public transient JTabbedPane tabs = new JTabbedPane(); // system menu items - FIXME make all menus here transient JMenuItem recording = new JMenuItem("start recording"); transient JMenuItem loadRecording = new JMenuItem("load recording"); final public String welcomeTabText = "Welcome"; // TODO - make MTOD !! from internet public transient GUIServiceGUI guiServiceGUI = null; // FIXME - supply Welcome type - create WelcomeGUI type - with // boundServiceName this GUIService transient Welcome welcome = null; transient HashMap<String, ServiceGUI> serviceGUIMap = new HashMap<String, ServiceGUI>(); boolean isDisplaying = false; transient JLabel status = new JLabel("status"); 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 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; } static public void restart() { JFrame frame = new JFrame(); 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 GUIService(String n) { super(n); Runtime.getInstance().addListener("registered", n, "registered"); Runtime.getInstance().addListener("released", n, "released"); // TODO - add the release route too // load();// <-- HA was looking all over for it } public void about() { new AboutDialog(this); } @Override public void actionPerformed(ActionEvent ae) { String cmd = ae.getActionCommand(); Object source = ae.getSource(); 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)) { } else if ("about".equals(cmd)) { new AboutDialog(this); // display(); } else if (source == recording) { if ("start recording".equals(recording.getText())) { startRecording(); recording.setText("stop recording"); } else { stopMsgRecording(); recording.setText("start recording"); } } else if (source == loadRecording) { log.error("load recording no longer supported"); /* * JFileChooser c = new JFileChooser(cfgDir); FileNameExtensionFilter * filter = new FileNameExtensionFilter("Message files", "msg"); * c.setFileFilter(filter); // Demonstrate "Open" dialog: String filename; * String dir; int rVal = c.showOpenDialog(frame); if (rVal == * JFileChooser.APPROVE_OPTION) { filename = * c.getSelectedFile().getName(); dir = * c.getCurrentDirectory().toString(); loadRecording(dir + "/" + * filename); } if (rVal == JFileChooser.CANCEL_OPTION) { * * } */ } else { log.info(String.format("unknown command %s", cmd)); } } /** * add a service tab to the GUIService * * @param serviceName * - name of service to add * * FIXME - full parameter of addTab(final String serviceName, final * String serviceType, final String lable) then overload */ synchronized public void addTab(final String serviceName) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { ServiceInterface sw = Runtime.getService(serviceName); if (sw == null) { log.error(String.format("addTab %1$s can not proceed - %1$s does not exist in registry (yet?)", serviceName)); return; } // get service type class name TODO String guiClass = String.format("org.myrobotlab.control.%sGUI", sw.getClass().getSimpleName()); if (serviceGUIMap.containsKey(sw.getName())) { log.debug(String.format("not creating %1$s gui - it already exists", sw.getName())); return; } ServiceGUI newGUI = createTabbedPanel(serviceName, guiClass, sw); // woot - got index ! int index = tabs.indexOfTab(serviceName) - 1; if (newGUI != null) { ++index; } guiServiceGUI = (GUIServiceGUI) serviceGUIMap.get(getName()); if (guiServiceGUI != null) { guiServiceGUI.rebuildGraph(); } Component c = tabs.getTabComponentAt(index); if (c instanceof TabControl2) { // TabControl2 tc = (TabControl2) c; if (!sw.isLocal()) { Color hsv = GUIService.getColorFromURI(sw.getInstanceId()); tabs.setBackgroundAt(index, hsv); } } frame.pack(); } }); } /** * Build the menu for display. * * @return */ public JMenuBar buildMenu() { JMenuBar menuBar = new JMenuBar(); JMenu help = new JMenu("help"); JMenuItem about = new JMenuItem("about"); about.addActionListener(this); help.add(about); menuBar.add(Box.createHorizontalGlue()); menuBar.add(help); return menuBar; } public JMenu buildRecordingMenu(JMenu parentMenu) { recording.addActionListener(this); parentMenu.add(recording); loadRecording.addActionListener(this); parentMenu.add(loadRecording); return parentMenu; } /** * builds all the service tabs for the first time called when GUIService * starts * * @return */ synchronized public JTabbedPane buildTabPanels() { // add the welcome screen if (!serviceGUIMap.containsKey(welcomeTabText)) { welcome = new Welcome(welcomeTabText, this, tabs); welcome.init(); serviceGUIMap.put(welcomeTabText, welcome); } Map<String, ServiceInterface> services = Runtime.getRegistry(); log.info("buildTabPanels service count " + Runtime.getRegistry().size()); TreeMap<String, ServiceInterface> sortedMap = new TreeMap<String, ServiceInterface>(services); Iterator<String> it = sortedMap.keySet().iterator(); synchronized (sortedMap) { // FIXED YAY !!!! while (it.hasNext()) { String serviceName = it.next(); addTab(serviceName); } } frame.pack(); return tabs; } /** * attempts to create a new ServiceGUI and add it to the map * * @param serviceName * @param guiClass * @param sw * @return */ public ServiceGUI createTabbedPanel(String serviceName, String guiClass, ServiceInterface sw) { ServiceGUI gui = null; ServiceInterface se = sw; gui = (ServiceGUI) Instantiator.getNewInstance(guiClass, se.getName(), this, tabs); if (gui == null) { log.info(String.format("could not construct a %s object - creating generic template", guiClass)); gui = (ServiceGUI) Instantiator.getNewInstance("org.myrobotlab.control._TemplateServiceGUI", se.getName(), this, tabs); } gui.init(); serviceGUIMap.put(serviceName, gui); gui.attachGUI(); // TODO - all auto-subscribtions could be done here subscribe(se.getName(), "publishStatus", getName(), "getStatus"); return gui; } @Override public void display() { if (!isDisplaying) { // reentrant if (frame != null) { frame.dispose(); frame = null; } if (frame == null) { frame = new JFrame(); } frame.addWindowListener(this); frame.setTitle("myrobotlab - " + getName() + " " + Runtime.getVersion().trim()); buildTabPanels(); JPanel main = new JPanel(new BorderLayout()); main.add(tabs, BorderLayout.CENTER); main.add(status, BorderLayout.SOUTH); status.setOpaque(true); frame.add(main); URL url = getClass().getResource("/resource/mrl_logo_36_36.png"); Toolkit kit = Toolkit.getDefaultToolkit(); Image img = kit.createImage(url); frame.setIconImage(img); // menu frame.setJMenuBar(buildMenu()); frame.setVisible(true); frame.pack(); isDisplaying = true; } } /** * closes window and puts the panel back into the tabbed pane */ public void dockPanel(final String label) { if (serviceGUIMap.containsKey(label)) { ServiceGUI sg = serviceGUIMap.get(label); sg.dockPanel(); } else { log.error("dockPanel - {} not in serviceGUIMap", label); } } public HashMap<String, mxCell> getCells() { return guiServiceGUI.serviceCells; } public String getDstMethodName() { return guiServiceGUI.dstMethodName.getText(); } public String getDstServiceName() { return guiServiceGUI.dstServiceName.getText(); } public JFrame getFrame() { return frame; } public mxGraph getGraph() { return guiServiceGUI.graph; } public String getGraphXML() { return graphXML; } public HashMap<String, ServiceGUI> getServiceGUIMap() { return serviceGUIMap; } public String getSrcMethodName() { return guiServiceGUI.srcMethodName.getText(); } public String getSrcServiceName() { return guiServiceGUI.srcServiceName.getText(); } public void getStatus(Status inStatus) { if (inStatus.isError()) { status.setOpaque(true); status.setForeground(Color.white); status.setBackground(Color.red); } else if (inStatus.isWarn()) { status.setOpaque(true); status.setForeground(Color.black); status.setBackground(Color.yellow); } else { status.setForeground(Color.black); status.setOpaque(false); } status.setText(inStatus.detail); } @Override public boolean hasDisplay() { return true; } public void hideAll() { log.info("hideAll"); // spin through all undocked for (Map.Entry<String, ServiceGUI> o : serviceGUIMap.entrySet()) { hidePanel(o.getKey()); } } // must handle docked or undocked public void hidePanel(final String label) { if (serviceGUIMap.containsKey(label)) { ServiceGUI sg = serviceGUIMap.get(label); sg.hidePanel(); } else { log.error("hidePanel - {} not in serviceGUIMap", label); } } public void noWorky() { // String img = // GUIService.class.getResource("/resource/expert.jpg").toString(); String logon = (String) JOptionPane.showInputDialog(getFrame(), "<html>This will send your myrobotlab.log file<br><p align=center>to our crack team of experts,<br> please type your myrobotlab.org user</p></html>", "No Worky!", JOptionPane.WARNING_MESSAGE, Util.getResourceIcon("expert.jpg"), null, null); if (logon == null || logon.length() == 0) { return; } try { if (Runtime.noWorky(logon).isInfo()) { JOptionPane.showMessageDialog(getFrame(), "log file sent, Thank you", "Sent !", JOptionPane.INFORMATION_MESSAGE); } else { JOptionPane.showMessageDialog(getFrame(), "could not send log file :(", "DOH !", JOptionPane.ERROR_MESSAGE); } } catch (Exception e1) { JOptionPane.showMessageDialog(getFrame(), Service.stackToString(e1), "DOH !", JOptionPane.ERROR_MESSAGE); } } public void pack() { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { frame.pack(); } }); } @Override public boolean preProcessHook(Message m) { // FIXME - problem with collisions of this service's methods // and dialog methods ?!?!? // if the method name is == to a method in the GUIService if (methodSet.contains(m.method)) { // process the message like a regular service return true; } // otherwise send the message to the dialog with the senders name ServiceGUI sg = serviceGUIMap.get(m.sender); if (sg == null) { log.error("attempting to update sub-gui - sender " + m.sender + " not available in map " + getName()); } else { // FIXME - NORMALIZE - Instantiator or Service - not both !!! // Instantiator.invokeMethod(serviceGUIMap.get(m.sender), m.method, // m.data); invokeOn(serviceGUIMap.get(m.sender), m.method, m.data); } return false; } public Service registered(final Service s) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { addTab(s.getName()); // kind of kludgy but got to keep them in sync RuntimeGUI rg = (RuntimeGUI) serviceGUIMap.get(Runtime.getInstance().getName()); if (rg != null) { rg.registered(s); } } }); return s; } // FIXME - now I think its only "register" - Deprecate if possible public void registerServicesEvent(String host, int port, Message msg) { buildTabPanels(); } public Service released(final Service s) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { removeTab(s.getName()); // kind of kludgy but got to keep them in sync RuntimeGUI rg = (RuntimeGUI) serviceGUIMap.get(Runtime.getInstance().getName()); if (rg != null) { rg.released(s); } } }); return s; } public void removeTab(final String name) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { log.info("removeTab"); // detaching & removing the ServiceGUI ServiceGUI sg = serviceGUIMap.get(name); if (sg != null) { sg.remove(); serviceGUIMap.remove(name); } else { log.warn(String.format("{} was not in the serviceGUIMap - unable to remove", name)); } guiServiceGUI = (GUIServiceGUI) serviceGUIMap.get(getName()); if (guiServiceGUI != null) { guiServiceGUI.rebuildGraph(); } frame.pack(); } }); } public void setArrow(String s) { guiServiceGUI.arrow0.setText(s); } public void setDstMethodName(String d) { guiServiceGUI.dstMethodName.setText(d); } public void setDstServiceName(String d) { guiServiceGUI.dstServiceName.setText(d); } public void setGraphXML(String xml) { graphXML = xml; } public void setPeriod0(String s) { guiServiceGUI.period0.setText(s); } public void setPeriod1(String s) { guiServiceGUI.period1.setText(s); } public void setSrcMethodName(String d) { guiServiceGUI.srcMethodName.setText(d); } public void setSrcServiceName(String d) { guiServiceGUI.srcServiceName.setText(d); } @Override public void startService() { super.startService(); display(); } @Override public void stopService() { if (frame != null) { frame.dispose(); } super.stopService(); } public void undockPanel(final String label) { if (serviceGUIMap.containsKey(label)) { ServiceGUI sg = serviceGUIMap.get(label); sg.undockPanel(); } else { log.error("undockPanel - {} not in serviceGUIMap", label); } } public void unhideAll() { log.info("unhideAll"); // spin through all undocked for (Map.Entry<String, ServiceGUI> o : serviceGUIMap.entrySet()) { unhidePanel(o.getKey()); } } // must handle docked or undocked & re-entrant for unhidden public void unhidePanel(final String label) { if (serviceGUIMap.containsKey(label)) { ServiceGUI sg = serviceGUIMap.get(label); sg.unhidePanel(); } else { log.error("unhidePanel - {} not in serviceGUIMap", label); } } // @Override - only in Java 1.6 @Override public void windowActivated(WindowEvent e) { // log.info("windowActivated"); } // @Override - only in Java 1.6 @Override public void windowClosed(WindowEvent e) { // log.info("windowClosed"); } // @Override - only in Java 1.6 @Override public void windowClosing(WindowEvent e) { // check for all service guis and see if its // ok to shutdown now Iterator<Map.Entry<String, ServiceGUI>> it = serviceGUIMap.entrySet().iterator(); while (it.hasNext()) { Map.Entry<String, ServiceGUI> pairs = it.next(); // String serviceName = pairs.getKey(); /* * if (undockedPanels.containsKey(serviceName)) { UndockedPanel up = * undockedPanels.get(serviceName); if (!up.isDocked()) { * up.savePosition(); } } */ ServiceGUI sg = pairs.getValue(); sg.savePosition(); sg.isReadyForRelease(); sg.makeReadyForRelease(); } save(); Runtime.releaseAll(); System.exit(1); // the Big Hamm'r } // @Override - only in Java 1.6 @Override public void windowDeactivated(WindowEvent e) { // log.info("windowDeactivated"); } // @Override - only in Java 1.6 @Override public void windowDeiconified(WindowEvent e) { // log.info("windowDeiconified"); } // @Override - only in Java 1.6 @Override public void windowIconified(WindowEvent e) { // log.info("windowActivated"); } // @Override - only in Java 1.6 @Override public void windowOpened(WindowEvent e) { // log.info("windowOpened"); } public static void main(String[] args) throws ClassNotFoundException, URISyntaxException { LoggingFactory.getInstance().configure(); Logging logging = LoggingFactory.getInstance(); try { logging.setLevel(Level.INFO); // Runtime.start("i01", "InMoov"); // Runtime.start("mac", "Runtime"); Runtime.start("python", "Python"); // RemoteAdapter remote = (RemoteAdapter)Runtime.start("remote", // "RemoteAdapter"); // remote.setDefaultPrefix("raspi"); // remote.connect("tcp://127.0.0.1:6767"); GUIService gui = (GUIService) Runtime.start("gui", "GUIService"); Runtime.start("python", "Python"); gui.startService(); } catch (Exception e) { Logging.logError(e); } } /** * This static method returns all the details of the class without it having * to be constructed. It has description, categories, dependencies, and peer * definitions. * * @return ServiceType - returns all the data * */ static public ServiceType getMetaData() { ServiceType meta = new ServiceType(GUIService.class.getCanonicalName()); meta.addDescription("Service used to graphically display and control other services"); meta.addCategory("location"); meta.addCategory("display"); // Its now part of myrobotlab.jar - unzipped in // build.xml (part of myrobotlab.jar now) // meta.addDependency("org.fife.autocomplete", "2.0.4"); // meta.addDependency("org.fife.rsyntaxtextarea","2.0.4.1"); // meta.addDependency("com.mxgraph.jgraph", "1.6.1.2"); return meta; } }