/** * * @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.control; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.DefaultListModel; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JRadioButtonMenuItem; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTable; import javax.swing.JToolBar; import javax.swing.JToolTip; import javax.swing.ListCellRenderer; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.border.TitledBorder; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; import javax.swing.table.TableColumn; import org.myrobotlab.control.widget.ProgressDialog; import org.myrobotlab.control.widget.Style; import org.myrobotlab.framework.Platform; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.framework.Status; import org.myrobotlab.framework.repo.Category; import org.myrobotlab.framework.repo.Repo; import org.myrobotlab.framework.repo.ServiceData; 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.net.BareBonesBrowserLaunch; import org.myrobotlab.service.GUIService; import org.myrobotlab.service.Runtime; import org.myrobotlab.service.interfaces.ServiceInterface; import org.slf4j.Logger; public class RuntimeGUI extends ServiceGUI implements ActionListener { // FIXME - checkingForUpdates needs to process ? versus display current // ServiceTypes class CellRenderer extends DefaultTableCellRenderer { private static final long serialVersionUID = 1L; @Override public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) { Repo repo = myRuntime.getRepo(); setEnabled(table == null || table.isEnabled()); Boolean availableToInstall = null; boolean upgradeAvailable = false; String upgradeString = "<html><h6>upgrade<br>"; // select by class being published by JTable on how to display if (value.getClass().equals(ServiceType.class)) { ServiceType entry = (ServiceType) table.getValueAt(row, 0); setHorizontalAlignment(SwingConstants.LEFT); setIcon(Util.getScaledIcon(Util.getImage((entry.getSimpleName() + ".png"), "unknown.png"), 0.50)); setText(entry.getSimpleName()); // setToolTipText("<html><body bgcolor=\"#E6E6FA\">" + // entry.type+ // " <a href=\"http://myrobotlab.org\">blah</a></body></html>"); } else if (value instanceof ServiceInterface) { ServiceInterface entry = (ServiceInterface) table.getValueAt(row, 0); setHorizontalAlignment(SwingConstants.LEFT); setIcon(Util.getScaledIcon(Util.getImage((entry.getSimpleName() + ".png"), "unknown.png"), 0.50)); setText(entry.getSimpleName()); } else if (value.getClass().equals(String.class)) { ServiceType entry = (ServiceType) table.getValueAt(row, 0); availableToInstall = repo.isServiceTypeInstalled(entry.getName()); setIcon(null); setHorizontalAlignment(SwingConstants.LEFT); if (!availableToInstall) { setText("<html><h6>not<br>installed </h6></html>"); } else { if (upgradeAvailable) { setText(upgradeString); } else { setText("<html><h6>latest </h6></html>"); } } } else { log.error("unknown class"); } if (possibleServices.isRowSelected(row)) { setBackground(Style.listHighlight); setForeground(Style.listForeground); } else { ServiceType entry = (ServiceType) table.getValueAt(row, 0); availableToInstall = repo.isServiceTypeInstalled(entry.getName()); if (!availableToInstall) { setForeground(Style.listForeground); setBackground(Style.possibleServicesNotInstalled); } else { if (upgradeAvailable) { setForeground(Style.listForeground); setBackground(Style.possibleServicesUpdate); } else { setForeground(Style.listForeground); setBackground(Style.possibleServicesStable); } } } // setBorder(BorderFactory.createEmptyBorder()); return this; } } class CurrentServicesRenderer extends JLabel implements ListCellRenderer { private static final long serialVersionUID = 1L; public CurrentServicesRenderer() { setOpaque(true); setIconTextGap(12); } @Override public Component getListCellRendererComponent(@SuppressWarnings("rawtypes") JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { ServiceInterface entry = (ServiceInterface) value; setText("<html><font color=#" + Style.listBackground + ">" + entry.getName() + "</font></html>"); ImageIcon icon = Util.getScaledIcon(Util.getImage((entry.getSimpleName() + ".png"), "unknown.png"), 0.50); setIcon(icon); if (isSelected) { setBackground(Style.listHighlight); setForeground(Style.listBackground); } else { setBackground(Style.listBackground); setForeground(Style.listForeground); } // log.info("getListCellRendererComponent - end"); return this; } } class FilterListener implements ActionListener { @Override public void actionPerformed(ActionEvent cmd) { log.info(cmd.getActionCommand()); if ("all".equals(cmd.getActionCommand())) { getPossibleServices(); } else { getPossibleServices(cmd.getActionCommand()); } } } public final static Logger log = LoggerFactory.getLogger(RuntimeGUI.class); static final long serialVersionUID = 1L; HashMap<String, ServiceInterface> nameToServiceEntry = new HashMap<String, ServiceInterface>(); ArrayList<String> resolveErrors = null; boolean localRepoChange = false; int popupRow = 0; JMenuItem infoMenuItem = null; JMenuItem installMenuItem = null; JMenuItem startMenuItem = null; JMenuItem upgradeMenuItem = null; JMenuItem releaseMenuItem = null; String possibleServiceFilter = null; ProgressDialog progressDialog = null; public Runtime myRuntime = null; public Repo myRepo = null; public ServiceData serviceData = null; DefaultListModel<ServiceInterface> currentServicesModel = new DefaultListModel<ServiceInterface>(); DefaultListModel<JButton> filterButtonModel = new DefaultListModel<JButton>(); JList<ServiceInterface> runningServices = new JList<ServiceInterface>(currentServicesModel); CurrentServicesRenderer currentServicesRenderer = new CurrentServicesRenderer(); FilterListener filterListener = new FilterListener(); JPopupMenu popup = new JPopupMenu(); ServiceInterface releasedTarget = null; DefaultTableModel possibleServicesModel = new DefaultTableModel() { private static final long serialVersionUID = 1L; @Override public boolean isCellEditable(int row, int column) { return false; } }; CellRenderer cellRenderer = new CellRenderer(); JTable possibleServices = new JTable(possibleServicesModel) { private static final long serialVersionUID = 1L; @Override public JToolTip createToolTip() { JToolTip tooltip = super.createToolTip(); return tooltip; } // column returns content type @Override public Class<?> getColumnClass(int column) { return getValueAt(0, column).getClass(); } }; public RuntimeGUI(final String boundServiceName, final GUIService myService, final JTabbedPane tabs) { super(boundServiceName, myService, tabs); // well done - this can be any runtime - which is good if there is // multiple instances myRuntime = (Runtime) Runtime.getService(boundServiceName); myRepo = myRuntime.getRepo(); serviceData = myRuntime.getServiceData(); } // zod @Override public void actionPerformed(ActionEvent event) { ServiceType c = (ServiceType) possibleServicesModel.getValueAt(popupRow, 0); String cmd = event.getActionCommand(); Object o = event.getSource(); if (releaseMenuItem == o) { myService.send(boundServiceName, "releaseService", releasedTarget.getName()); return; } if ("info".equals(cmd)) { BareBonesBrowserLaunch.openURL("http://myrobotlab.org/service/" + c.getSimpleName()); } else if ("install".equals(cmd)) { int selectedRow = possibleServices.getSelectedRow(); ServiceType entry = ((ServiceType) possibleServices.getValueAt(selectedRow, 0)); String n = entry.getName(); Repo repo = myRuntime.getRepo(); if (!repo.isServiceTypeInstalled(n)) { // dependencies needed !!! String msg = "<html>This Service has dependencies which are not yet loaded,<br>" + "do you wish to download them now?"; JOptionPane.setRootFrame(myService.getFrame()); int result = JOptionPane.showConfirmDialog(myService.getFrame(), msg, "alert", JOptionPane.OK_CANCEL_OPTION); if (result == JOptionPane.CANCEL_OPTION) { return; } // you say "install", i say "update", repo says "resolve" myService.send(boundServiceName, "install", n); } else { // no unfulfilled dependencies - good to go addNewService(n); } } else if ("start".equals(cmd)) { int selectedRow = possibleServices.getSelectedRow(); ServiceType entry = ((ServiceType) possibleServices.getValueAt(selectedRow, 0)); addNewService(entry.getName()); } else if ("install all".equals(cmd)) { myService.send(boundServiceName, "install"); } else if ("check for updates".equals(cmd)) { myService.send(myRuntime.getName(), "checkForUpdates"); } else if (cmd.equals(Level.DEBUG) || cmd.equals(Level.INFO) || cmd.equals(Level.WARN) || cmd.equals(Level.ERROR) || cmd.equals(Level.FATAL)) { Logging logging = LoggingFactory.getInstance(); logging.setLevel(cmd); } else if (cmd.equals(Appender.FILE)) { Logging logging = LoggingFactory.getInstance(); logging.addAppender(Appender.FILE); } else if (cmd.equals(Appender.CONSOLE)) { Logging logging = LoggingFactory.getInstance(); logging.addAppender(Appender.CONSOLE); } else if (cmd.equals(Appender.NONE)) { Logging logging = LoggingFactory.getInstance(); logging.removeAllAppenders(); } else { log.error("unknown command " + cmd); } // end actionCmd } public void addNewService(String newService) { JFrame frame = new JFrame(); frame.setTitle("add new service"); String name = JOptionPane.showInputDialog(frame, "new service name"); if (name != null && name.length() > 0) { myService.send(boundServiceName, "createAndStart", name, newService); } } @Override public void attachGUI() { subscribe("publishInstallProgress", "onInstallProgress"); } /** * Add all options to the Log Level menu. * * @param parentMenu */ private void buildLogLevelMenu(JMenu parentMenu) { ButtonGroup logLevelGroup = new ButtonGroup(); String level = LoggingFactory.getInstance().getLevel(); JRadioButtonMenuItem mi = new JRadioButtonMenuItem(Level.DEBUG); mi.setSelected(("DEBUG".equals(level))); mi.addActionListener(this); logLevelGroup.add(mi); parentMenu.add(mi); mi = new JRadioButtonMenuItem(Level.INFO); mi.setSelected(("INFO".equals(level))); mi.addActionListener(this); logLevelGroup.add(mi); parentMenu.add(mi); mi = new JRadioButtonMenuItem(Level.WARN); mi.setSelected(("WARN".equals(level))); mi.addActionListener(this); logLevelGroup.add(mi); parentMenu.add(mi); mi = new JRadioButtonMenuItem(Level.ERROR); mi.setSelected(("ERROR".equals(level))); mi.addActionListener(this); logLevelGroup.add(mi); parentMenu.add(mi); mi = new JRadioButtonMenuItem(Level.FATAL); // TODO - deprecate to WTF // :) mi.setSelected(("FATAL".equals(level))); mi.addActionListener(this); logLevelGroup.add(mi); parentMenu.add(mi); } public void checkingForUpdates() { // FIXME - if auto update or check on startup - we don't want a silly // dialog // we just want to be notified if there "is" an update - and whether or // not we // should apply it // FIXME - bypass is auto progressDialog.checkingForUpdates(); } @Override public void detachGUI() { unsubscribe("publishInstallProgress", "onInstallProgress"); } public void failedDependency(String dep) { JOptionPane.showMessageDialog(null, "<html>Unable to load Service...<br>" + dep + "</html>", "Error", JOptionPane.ERROR_MESSAGE); } /** * Add data to the list model for display */ public void getCurrentServices() { Map<String, ServiceInterface> services = Runtime.getRegistry(); Map<String, ServiceInterface> sortedMap = null; sortedMap = new TreeMap<String, ServiceInterface>(services); Iterator<String> it = sortedMap.keySet().iterator(); while (it.hasNext()) { String serviceName = it.next(); ServiceInterface si = Runtime.getService(serviceName); currentServicesModel.addElement(si); nameToServiceEntry.put(serviceName, si); } } public void getPossibleServices() { getPossibleServices(null); } /** * lame - deprecate - refactor - or better yet make webgui FIXME this should * rarely change .... remove getServiceTypeNames * * @param serviceTypeNames */ public void getPossibleServices(final String filter) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // clear data for (int i = possibleServicesModel.getRowCount(); i > 0; --i) { possibleServicesModel.removeRow(i - 1); } Category category = serviceData.getCategory(filter); HashSet<String> filtered = null; if (category != null) { filtered = new HashSet<String>(); ArrayList<String> f = category.serviceTypes; for (int i = 0; i < f.size(); ++i) { filtered.add(f.get(i)); } } // populate with serviceData ArrayList<ServiceType> possibleService = serviceData.getServiceTypes(); for (int i = 0; i < possibleService.size(); ++i) { ServiceType serviceType = possibleService.get(i); if (filtered == null || filtered.contains(serviceType.getName())) { if (serviceType.isAvailable()) { possibleServicesModel.addRow(new Object[] { serviceType, "" }); } } } possibleServicesModel.fireTableDataChanged(); possibleServices.invalidate(); } }); } public void getState(final Runtime runtime) { myRuntime = runtime; SwingUtilities.invokeLater(new Runnable() { @Override public void run() { // FIXME - change to "all" or "" - null is sloppy - system has // to upcast } }); } @Override public void init() { display.setLayout(new BorderLayout()); progressDialog = new ProgressDialog(this); progressDialog.setVisible(false); getCurrentServices(); runningServices.setCellRenderer(currentServicesRenderer); runningServices.setFixedCellWidth(200); possibleServicesModel.addColumn(""); possibleServicesModel.addColumn(""); possibleServices.setRowHeight(24); possibleServices.setIntercellSpacing(new Dimension(0, 0)); possibleServices.setShowGrid(false); possibleServices.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); TableColumn col = possibleServices.getColumnModel().getColumn(1); col.setPreferredWidth(10); possibleServices.setPreferredScrollableViewportSize(new Dimension(300, 480)); // set map to determine what types get rendered possibleServices.setDefaultRenderer(ImageIcon.class, cellRenderer); possibleServices.setDefaultRenderer(ServiceType.class, cellRenderer); possibleServices.setDefaultRenderer(String.class, cellRenderer); possibleServices.addMouseListener(new MouseAdapter() { // isPopupTrigger over OSs - use masks @Override public void mouseReleased(MouseEvent e) { log.debug("mouseReleased"); if (SwingUtilities.isRightMouseButton(e)) { log.debug("mouseReleased - right"); popUpTrigger(e); } } public void popUpTrigger(MouseEvent e) { log.info("******************popUpTrigger*********************"); JTable source = (JTable) e.getSource(); popupRow = source.rowAtPoint(e.getPoint()); ServiceType c = (ServiceType) possibleServicesModel.getValueAt(popupRow, 0); releaseMenuItem.setVisible(false); infoMenuItem.setVisible(true); if (!myRepo.isServiceTypeInstalled(c.getName())) { // need to install it installMenuItem.setVisible(true); startMenuItem.setVisible(false); upgradeMenuItem.setVisible(false); } else { // have it installMenuItem.setVisible(false); startMenuItem.setVisible(true); } int column = source.columnAtPoint(e.getPoint()); if (!source.isRowSelected(popupRow)) source.changeSelection(popupRow, column, false, false); popup.show(e.getComponent(), e.getX(), e.getY()); } }); runningServices.addMouseListener(new MouseAdapter() { // isPopupTrigger over OSs - use masks @Override public void mouseReleased(MouseEvent e) { log.debug("mouseReleased"); if (SwingUtilities.isRightMouseButton(e)) { log.debug("mouseReleased - right"); popUpTrigger(e); } } public void popUpTrigger(MouseEvent e) { log.info("******************popUpTrigger*********************"); JList source = (JList) e.getSource(); int index = source.locationToIndex(e.getPoint()); if (index >= 0) { releasedTarget = (ServiceInterface) source.getModel().getElementAt(index); log.info(String.format("right click on running service %s", releasedTarget.getName())); releaseMenuItem.setVisible(true); upgradeMenuItem.setVisible(false); installMenuItem.setVisible(false); startMenuItem.setVisible(false); infoMenuItem.setVisible(false); } popup.show(e.getComponent(), e.getX(), e.getY()); } }); infoMenuItem = new JMenuItem("<html><style type=\"text/css\">a { color: #000000;text-decoration: none}</style><a href=\"http://myrobotlab.org/\">info</a></html>"); infoMenuItem.setActionCommand("info"); infoMenuItem.setIcon(Util.getImageIcon("help.png")); infoMenuItem.addActionListener(this); popup.add(infoMenuItem); installMenuItem = new JMenuItem("install"); installMenuItem.addActionListener(this); installMenuItem.setIcon(Util.getImageIcon("install.png")); popup.add(installMenuItem); startMenuItem = new JMenuItem("start"); startMenuItem.addActionListener(this); startMenuItem.setIcon(Util.getImageIcon("start.png")); popup.add(startMenuItem); upgradeMenuItem = new JMenuItem("upgrade"); upgradeMenuItem.addActionListener(this); upgradeMenuItem.setIcon(Util.getImageIcon("upgrade.png")); popup.add(upgradeMenuItem); releaseMenuItem = new JMenuItem("release"); releaseMenuItem.addActionListener(this); releaseMenuItem.setIcon(Util.getScaledIcon(Util.getImage("release.png"), 0.50)); popup.add(releaseMenuItem); JScrollPane runningServicesScrollPane = new JScrollPane(runningServices); JScrollPane possibleServicesScrollPane = new JScrollPane(possibleServices); runningServices.setVisibleRowCount(20); // make category filter buttons JPanel filters = new JPanel(new GridBagLayout()); GridBagConstraints fgc = new GridBagConstraints(); ++fgc.gridy; fgc.fill = GridBagConstraints.HORIZONTAL; filters.add(new JLabel("category filters"), fgc); ++fgc.gridy; if (myRuntime != Runtime.getInstance()) { log.info("foreign runtime"); } // category toolbar ArrayList<Category> cats = serviceData.getCategories(); JPanel flowLayout = new JPanel(); flowLayout.setPreferredSize(new Dimension(300, 160)); JToolBar toolbar = new JToolBar(); JButton all = new JButton("all"); all.addActionListener(filterListener); toolbar.add(all); int t = 0; for (int j = 0; j < cats.size(); ++j) { t += 1; JButton b = new JButton(cats.get(j).name); b.addActionListener(filterListener); toolbar.add(b); if (t % 8 == 0) { flowLayout.add(toolbar); toolbar = new JToolBar(); } } flowLayout.add(toolbar); JPanel possibleServicesPanel = new JPanel(new GridBagLayout()); fgc.gridy = 0; fgc.gridx = 0; fgc.fill = GridBagConstraints.HORIZONTAL; possibleServicesPanel.add(new JLabel("possible services"), fgc); ++fgc.gridy; possibleServicesPanel.add(possibleServicesScrollPane, fgc); JPanel runningServicesPanel = new JPanel(new GridBagLayout()); fgc.gridy = 0; fgc.gridx = 0; fgc.fill = GridBagConstraints.HORIZONTAL; runningServicesPanel.add(new JLabel("running services"), fgc); ++fgc.gridy; runningServicesPanel.add(runningServicesScrollPane, fgc); JPanel center = new JPanel(); center.add(filters); center.add(possibleServicesPanel); center.add(runningServicesPanel); TitledBorder title; Platform platform = myRuntime.getPlatform(); // TODO - get memory total & free - put in Platform? getMemory has to be // implemented as a callback title = BorderFactory.createTitledBorder(String.format("%s %s", platform.getPlatformId(), platform.getVersion())); center.setBorder(title); JMenuBar menuBar = new JMenuBar(); JMenu system = new JMenu("system"); menuBar.add(system); JMenu logging = new JMenu("logging"); menuBar.add(logging); JMenuItem item = new JMenuItem("check for updates"); item.addActionListener(this); system.add(item); item = new JMenuItem("install all"); item.addActionListener(this); system.add(item); item = new JMenuItem("record"); item.addActionListener(this); system.add(item); JMenu m1 = new JMenu("level"); logging.add(m1); buildLogLevelMenu(m1); display.add(menuBar, BorderLayout.NORTH); display.add(center, BorderLayout.CENTER); display.add(flowLayout, BorderLayout.SOUTH); getPossibleServices(); } /** * new Service has been created list it.. * * @param sw * @return */ public ServiceInterface registered(Service sw) { if (!nameToServiceEntry.containsKey(sw.getName())) { currentServicesModel.addElement(sw); nameToServiceEntry.put(sw.getName(), sw); } return sw; } /** * a Service of this Runtime has been released * * @param sw * @return */ public ServiceInterface released(Service sw) { if (nameToServiceEntry.containsKey(sw.getName())) { currentServicesModel.removeElement(nameToServiceEntry.get(sw.getName())); } else { log.error(sw.getName() + " released event - but could not find in currentServiceModel"); } return sw; } /** * a restart command will be sent to the appropriate runtime */ public void restart() { send("restart"); } public void onInstallProgress(Status status) { if (Repo.INSTALL_START.equals(status.key)) { progressDialog.beginUpdates(); } else if (Repo.INSTALL_FINISHED.equals(status.key)) { progressDialog.finished(); } progressDialog.addStatus(status); } /** * this is the beginning of the applyUpdates process * * @return */ public void updatesBegin() { progressDialog.beginUpdates(); } }