All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.praxislive.ide.project.ui.HubUITopComponent Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2020 Neil C Smith.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 3 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 3 for more details.
 *
 * You should have received a copy of the GNU General Public License version 3
 * along with this work; if not, see http://www.gnu.org/licenses/
 *
 *
 * Please visit https://www.praxislive.org if you need additional information or
 * have any questions.
 */
package org.praxislive.ide.project.ui;

import java.awt.Image;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ListSelectionModel;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.openide.util.NbBundle;
import org.openide.windows.TopComponent;
import org.openide.windows.WindowManager;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.explorer.ExplorerManager;
import org.openide.explorer.view.ListView;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.praxislive.core.protocols.StartableProtocol;
import org.praxislive.core.types.PBoolean;
import org.praxislive.ide.model.HubProxy;
import org.praxislive.ide.model.RootProxy;
import org.praxislive.ide.properties.PraxisProperty;

/**
 * Top component which displays Hub and roots information
 */
@ConvertAsProperties(dtd = "-//org.praxislive.ide.hubui//HubUI//EN",
        autostore = false)
@NbBundle.Messages({
    "LBL_startAction=Start",
    "LBL_stopAction=Stop",
    "LBL_rootActive=[active]",
    "LBL_rootIdle=[idle]"
})
public final class HubUITopComponent extends TopComponent implements ExplorerManager.Provider {

    private final static String SYSTEM_PREFIX = "_";

    private static final String PREFERRED_ID = "HubUITopComponent";

    private final static String RESOURCE_DIR = "org/praxislive/ide/project/resources/";

    /**
     * path to the icon used by the component and its open action
     */
    private static final String ICON_PATH = RESOURCE_DIR + "hub-action.png";

    private static HubUITopComponent instance;

    private final ExplorerManager manager;
    private final TCListener registryListener;

    private HubNode hubNode;

    public HubUITopComponent() {
        manager = new ExplorerManager();
        initComponents();
        ((ListView) rootList).setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        setName(NbBundle.getMessage(HubUITopComponent.class, "CTL_HubUITopComponent"));
        setToolTipText(NbBundle.getMessage(HubUITopComponent.class, "HINT_HubUITopComponent"));
        setIcon(ImageUtilities.loadImage(ICON_PATH, true));
        registryListener = new TCListener();
    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // //GEN-BEGIN:initComponents
    private void initComponents() {

        rootList = new ListView();
        jToolBar1 = new javax.swing.JToolBar();
        systemRootToggle = new javax.swing.JToggleButton();

        jToolBar1.setBorder(javax.swing.BorderFactory.createEmptyBorder(1, 1, 1, 1));
        jToolBar1.setFloatable(false);
        jToolBar1.setRollover(true);

        systemRootToggle.setIcon(new javax.swing.ImageIcon(getClass().getResource("/org/praxislive/ide/project/resources/system24.png"))); // NOI18N
        systemRootToggle.setToolTipText(org.openide.util.NbBundle.getMessage(HubUITopComponent.class, "LBL_ShowSystemRoots")); // NOI18N
        systemRootToggle.setActionCommand("showSystemRoots");
        systemRootToggle.setFocusable(false);
        systemRootToggle.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
        systemRootToggle.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM);
        systemRootToggle.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                systemRootToggleActionPerformed(evt);
            }
        });
        jToolBar1.add(systemRootToggle);
        systemRootToggle.getAccessibleContext().setAccessibleDescription(org.openide.util.NbBundle.getMessage(HubUITopComponent.class, "LBL_ShowSystemRoots")); // NOI18N

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
            .addComponent(rootList, javax.swing.GroupLayout.DEFAULT_SIZE, 200, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addComponent(rootList, javax.swing.GroupLayout.DEFAULT_SIZE, 200, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jToolBar1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
    }// //GEN-END:initComponents

    private void systemRootToggleActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_systemRootToggleActionPerformed
        if (hubNode != null) {
            hubNode.roots.showSystem(systemRootToggle.isSelected());
        }
    }//GEN-LAST:event_systemRootToggleActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JToolBar jToolBar1;
    private javax.swing.JScrollPane rootList;
    private javax.swing.JToggleButton systemRootToggle;
    // End of variables declaration//GEN-END:variables

    /**
     * Gets default instance. Do not use directly: reserved for *.settings files
     * only, i.e. deserialization routines; otherwise you could get a
     * non-deserialized instance. To obtain the singleton instance, use
     * {@link #findInstance}.
     */
    public static synchronized HubUITopComponent getDefault() {
        if (instance == null) {
            instance = new HubUITopComponent();
        }
        return instance;
    }

    /**
     * Obtain the HubUITopComponent instance. Never call {@link #getDefault}
     * directly!
     */
    public static synchronized HubUITopComponent findInstance() {
        TopComponent win = WindowManager.getDefault().findTopComponent(PREFERRED_ID);
        if (win == null) {
            Logger.getLogger(HubUITopComponent.class.getName()).warning(
                    "Cannot find " + PREFERRED_ID + " component. It will not be located properly in the window system.");
            return getDefault();
        }
        if (win instanceof HubUITopComponent) {
            return (HubUITopComponent) win;
        }
        Logger.getLogger(HubUITopComponent.class.getName()).warning(
                "There seem to be multiple components with the '" + PREFERRED_ID
                + "' ID. That is a potential source of errors and unexpected behavior.");
        return getDefault();
    }

    @Override
    public ExplorerManager getExplorerManager() {
        return manager;
    }

    @Override
    public int getPersistenceType() {
        return TopComponent.PERSISTENCE_ALWAYS;
    }

    @Override
    public void componentOpened() {
        TopComponent.getRegistry().addPropertyChangeListener(registryListener);
        refresh();
    }

    @Override
    public void componentClosed() {
        TopComponent.getRegistry().removePropertyChangeListener(registryListener);
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    Object readProperties(java.util.Properties p) {
        if (instance == null) {
            instance = this;
        }
        instance.readPropertiesImpl(p);
        return instance;
    }

    private void readPropertiesImpl(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version
    }

    @Override
    protected String preferredID() {
        return PREFERRED_ID;
    }

    private void refresh() {
        Project project = null;
        boolean foundMany = false;

        Node[] nodes = TopComponent.getRegistry().getActivatedNodes();
        for (Node node : nodes) {
            Lookup lkp = node.getLookup();
            var found = findProject(lkp);

            if (found != null) {
                if (project != null && project != found) {
                    // more than one
                    project = null;
                    foundMany = true;
                    break;
                } else {
                    project = found;
                }
            }
        }

        if (project == null && !foundMany) {
            var tc = TopComponent.getRegistry().getActivated();
            if (tc != null) {
                project = findProject(tc.getLookup());
            }
        }

        HubProxy hub;
        if (project != null) {
            hub = project.getLookup().lookup(HubProxy.class);
        } else {
            hub = null;
        }

        if (hub != null) {
            if (hubNode != null) {
                if (hubNode.hub == hub) {
                    return;
                } else {
                    hubNode.dispose();
                }
            }
            hubNode = new HubNode(hub, hub.getNodeDelegate());
            manager.setRootContext(hubNode);
            hubNode.roots.showSystem(systemRootToggle.isSelected());
        } else if (foundMany) {
            if (hubNode == null) {
                return;
            } else {
                hubNode.dispose();
                hubNode = null;
                manager.setRootContext(new AbstractNode(Children.LEAF));
            }
        }
    }

    private Project findProject(Lookup lkp) {
        var project = lkp.lookup(Project.class);
        if (project == null) {
            var dob = lkp.lookup(DataObject.class);
            if (dob != null) {
                project = FileOwnerQuery.getOwner(dob.getPrimaryFile());
            }
        }
        return project;
    }

    private class TCListener implements PropertyChangeListener {

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if (TopComponent.Registry.PROP_ACTIVATED_NODES.equals(evt.getPropertyName())) {
                refresh();
            }
        }

    }

    private static class HubNode extends FilterNode {

        final HubProxy hub;
        final Roots roots;

        private HubNode(HubProxy hub, Node original) {
            this(hub, original, new Roots(original));
        }

        private HubNode(HubProxy hub, Node original, Roots roots) {
            super(original, roots);
            this.hub = hub;
            this.roots = roots;
        }

        private void dispose() {
            roots.dispose();
        }

    }
    
    private static class Roots extends FilterNode.Children {

        private boolean showSystem;

        Roots(Node original) {
            super(original);
        }

        @Override
        protected Node[] createNodes(Node key) {
            RootProxy root = key.getLookup().lookup(RootProxy.class);
            if (root == null || (key.isExpert() && !showSystem)) {
                return new Node[0];
            } else {
                return new Node[]{new RootNode(key, root)};
            }

        }

        private void showSystem(boolean showSystem) {
            this.showSystem = showSystem;
            Node[] nodes = original.getChildren().getNodes(true);
            for (Node node : nodes) {
                refreshKey(node);
            }
        }
        
        private void dispose() {
            setKeys(new Node[0]);
        }

    }

    private static class RootNode extends FilterNode
            implements PropertyChangeListener {
        
        private static final Image ROOT_ACTIVE_ICON = 
                ImageUtilities.loadImage(RESOURCE_DIR + "root_active.png", true);
        private static final Image ROOT_IDLE_ICON = 
                ImageUtilities.loadImage(RESOURCE_DIR + "root_idle.png", true);
        private static final Image ROOT_ICON = 
                ImageUtilities.loadImage(RESOURCE_DIR + "root.png", true);

        private final RootProxy root;
        private final boolean startable;
        private final Action startAction;
        private final Action stopAction;
        
        boolean running;

        private RootNode(Node original, RootProxy root) {
            super(original, Children.LEAF);
            this.root = root;
            startable = root.getInfo().hasProtocol(StartableProtocol.class);
            startAction = new StartableAction(Bundle.LBL_startAction(), root, true);
            startAction.setEnabled(false);
            stopAction = new StartableAction(Bundle.LBL_stopAction(), root, false);
            stopAction.setEnabled(false);
            root.addPropertyChangeListener(this);
            disableDelegation(DELEGATE_SET_DISPLAY_NAME | DELEGATE_GET_DISPLAY_NAME);
            refresh();
        }

        @Override
        public void destroy() throws IOException {
            super.destroy();
            root.removePropertyChangeListener(this);
        }

        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            if ("is-running".equals(evt.getPropertyName())) {
                refresh();
            }
        }

        @Override
        public Action getPreferredAction() {
            if (startAction.isEnabled()) {
                return startAction;
            } else if (stopAction.isEnabled()) {
                return stopAction;
            } else {
                return null;
            }
        }
        
        @Override
        public Action[] getActions(boolean context) {
            if (startable) {
                return new Action[]{startAction, stopAction};
            } else {
                return new Action[0];
            }
        }

        private void refresh() {
            var id = root.getAddress().rootID();
            if (startable) {
                running = Optional.ofNullable(root.getProperty("is-running"))
                        .map(PraxisProperty::getValue)
                        .flatMap(PBoolean::from)
                        .orElse(PBoolean.FALSE).value();
                if (running) {
                    setDisplayName(id + " " + Bundle.LBL_rootActive());
                    startAction.setEnabled(false);
                    stopAction.setEnabled(true);
                } else {
                    setDisplayName(id + " " + Bundle.LBL_rootIdle());
                    startAction.setEnabled(true);
                    stopAction.setEnabled(false);
                }
            } else {
                running = false;
                setDisplayName(id);
            }
            fireIconChange();
        }

        @Override
        public Image getIcon(int type) {
            if (startable) {
                if (running) {
                    return ROOT_ACTIVE_ICON;
                } else {
                    return ROOT_IDLE_ICON;
                }
            } else {
                return ROOT_ICON;
            }
        }

    }
    
    private static class StartableAction extends AbstractAction {
        
        private final RootProxy root;
        private final boolean start;
        
        private StartableAction(String name, RootProxy root, boolean start) {
            super(name);
            this.root = root;
            this.start = start;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            root.send(start ? StartableProtocol.START : StartableProtocol.STOP,
                    List.of());
        }
        
    }

    

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy