blob: 44d1685dcc1cd039467bc5c4a0bfb96ffffc4ee8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.javascript.karma.ui.logicalview;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JRadioButtonMenuItem;
import javax.swing.event.ChangeListener;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.project.Project;
import org.netbeans.modules.javascript.karma.exec.KarmaServers;
import org.netbeans.modules.javascript.karma.exec.KarmaServersListener;
import org.netbeans.modules.javascript.karma.preferences.KarmaPreferences;
import org.netbeans.modules.javascript.karma.preferences.KarmaPreferencesValidator;
import org.netbeans.modules.javascript.karma.util.FileUtils;
import org.netbeans.modules.javascript.karma.util.KarmaUtils;
import org.netbeans.modules.web.clientproject.api.jstesting.JsTestingProviders;
import org.netbeans.modules.web.common.api.ValidationResult;
import org.netbeans.spi.project.ui.CustomizerProvider2;
import org.netbeans.spi.project.ui.support.NodeList;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
import org.openide.awt.DynamicMenuContent;
import org.openide.awt.Mnemonics;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
import org.openide.util.ChangeSupport;
import org.openide.util.ContextAwareAction;
import org.openide.util.HelpCtx;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.WeakListeners;
import org.openide.util.actions.CallableSystemAction;
import org.openide.util.actions.NodeAction;
import org.openide.util.actions.Presenter;
import org.openide.util.actions.SystemAction;
import org.openide.util.lookup.Lookups;
public class KarmaChildrenList implements NodeList<Node>, PreferenceChangeListener {
static final Logger LOGGER = Logger.getLogger(KarmaChildrenList.class.getName());
private final Project project;
private final ChangeSupport changeSupport = new ChangeSupport(this);
public KarmaChildrenList(Project project) {
assert project != null;
this.project = project;
}
@Override
public List<Node> keys() {
if (KarmaPreferences.isEnabled(project)) {
return Collections.<Node>singletonList(KarmaNode.create(project));
}
return Collections.emptyList();
}
@Override
public void addChangeListener(ChangeListener listener) {
changeSupport.addChangeListener(listener);
}
@Override
public void removeChangeListener(ChangeListener listener) {
changeSupport.removeChangeListener(listener);
}
@Override
public Node node(Node key) {
return key;
}
@Override
public void addNotify() {
KarmaPreferences.addPreferenceChangeListener(project, WeakListeners.create(PreferenceChangeListener.class, this, KarmaPreferences.class));
}
@Override
public void removeNotify() {
// noop
}
@Override
public void preferenceChange(PreferenceChangeEvent evt) {
if (!KarmaPreferences.isDebug(project)) {
// possibly close browser tab
KarmaServers.getInstance().closeDebugUrl(project);
}
// possibly restart server
if (KarmaServers.getInstance().isServerRunning(project)) {
KarmaServers.getInstance().stopServer(project, false);
if (KarmaPreferences.isEnabled(project)) {
ValidationResult result = new KarmaPreferencesValidator()
.validate(project)
.getResult();
if (result.isFaultless()) {
KarmaServers.getInstance().startServer(project);
}
}
}
changeSupport.fireChange();
}
//~ Inner classes
private static final class KarmaNode extends AbstractNode implements KarmaServersListener {
@StaticResource
private static final String KARMA_ICON = "org/netbeans/modules/javascript/karma/ui/resources/karma.png"; // NOI18N
@StaticResource
private static final String WAITING_BADGE = "org/netbeans/modules/javascript/karma/ui/resources/waiting.png"; // NOI18N
@StaticResource
private static final String RUNNING_BADGE = "org/netbeans/modules/javascript/karma/ui/resources/running.png"; // NOI18N
private final Project project;
@NbBundle.Messages({
"KarmaNode.displayName=Karma",
"KarmaNode.description=Test Runner for JavaScript",
})
private KarmaNode(Project project) {
super(Children.LEAF, Lookups.fixed(project));
assert project != null;
this.project = project;
setName("Karma"); // NOI18N
setDisplayName(Bundle.KarmaNode_displayName());
setShortDescription(Bundle.KarmaNode_description());
setIconBaseWithExtension(KARMA_ICON);
}
static KarmaNode create(Project project) {
KarmaNode karmaNode = new KarmaNode(project);
KarmaServers.getInstance().addKarmaServersListener(karmaNode);
return karmaNode;
}
@Override
public void destroy() throws IOException {
KarmaServers.getInstance().removeKarmaServersListener(this);
super.destroy();
}
@Override
public Action[] getActions(boolean context) {
return new Action[] {
SystemAction.get(StartKarmaServerAction.class),
SystemAction.get(StopKarmaServerAction.class),
SystemAction.get(RestartKarmaServerAction.class),
null,
SystemAction.get(DebugKarmaServerAction.class),
null,
SystemAction.get(ActiveKarmaConfigAction.class),
null,
SystemAction.get(CustomizeKarmaAction.class),
};
}
@Override
public Image getIcon(int type) {
return badgeIcon(super.getIcon(type));
}
@Override
public Image getOpenedIcon(int type) {
return badgeIcon(super.getOpenedIcon(type));
}
private Image badgeIcon(Image origImg) {
Image badge = null;
if (KarmaServers.getInstance().isServerStarting(project)) {
badge = ImageUtilities.loadImage(WAITING_BADGE);
} else if (KarmaServers.getInstance().isServerStarted(project)) {
badge = ImageUtilities.loadImage(RUNNING_BADGE);
}
return badge != null ? ImageUtilities.mergeImages(origImg, badge, 15, 8) : origImg;
}
@Override
public void serverStateChanged(Project project) {
if (this.project.equals(project)) {
fireIconChange();
fireOpenedIconChange();
}
}
}
private static final class StartKarmaServerAction extends BaseNodeAction {
public StartKarmaServerAction() {
}
@Override
protected void performAction(Project project) {
KarmaServers.getInstance().startServer(project);
}
@Override
protected boolean enable(Project project) {
return !KarmaServers.getInstance().isServerRunning(project);
}
@NbBundle.Messages("StartKarmaServerAction.name=Start")
@Override
public String getName() {
return Bundle.StartKarmaServerAction_name();
}
}
private static final class StopKarmaServerAction extends BaseNodeAction {
public StopKarmaServerAction() {
}
@Override
protected void performAction(Project project) {
KarmaServers.getInstance().stopServer(project, false);
}
@Override
protected boolean enable(Project project) {
return KarmaServers.getInstance().isServerRunning(project);
}
@NbBundle.Messages("StopKarmaServerAction.name=Stop")
@Override
public String getName() {
return Bundle.StopKarmaServerAction_name();
}
}
private static final class RestartKarmaServerAction extends BaseNodeAction {
public RestartKarmaServerAction() {
}
@Override
protected void performAction(Project project) {
KarmaServers.getInstance().restartServer(project);
}
@Override
protected boolean enable(Project project) {
return KarmaServers.getInstance().isServerRunning(project);
}
@NbBundle.Messages("RestartKarmaServerAction.name=Restart")
@Override
public String getName() {
return Bundle.RestartKarmaServerAction_name();
}
}
private static final class DebugKarmaServerAction extends CallableSystemAction implements ContextAwareAction {
public DebugKarmaServerAction() {
}
@NbBundle.Messages("DebugKarmaServerAction.name=Debug")
@Override
public String getName() {
return Bundle.DebugKarmaServerAction_name();
}
@Override
public void performAction() {
assert false;
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
@NbBundle.Messages("DebugKarmaServerAction.browser.none=Confirm Karma debug properties first")
@Override
public Action createContextAwareInstance(Lookup actionContext) {
final class DebugAction extends AbstractAction implements Presenter.Popup {
private final Project project;
private DebugAction(Lookup actionContext) {
project = actionContext.lookup(Project.class);
assert project != null : "Project expected in lookup: " + actionContext;
putValue(NAME, Bundle.DebugKarmaServerAction_name());
}
@Override
public void actionPerformed(ActionEvent e) {
boolean originalDebug = KarmaPreferences.isDebug(project);
if (!originalDebug
&& !KarmaPreferences.isDebugBrowserIdSet(project)) {
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message(Bundle.DebugKarmaServerAction_browser_none()));
project.getLookup().lookup(CustomizerProvider2.class).showCustomizer(JsTestingProviders.CUSTOMIZER_IDENT, null);
return;
}
KarmaPreferences.setDebug(project, !originalDebug);
}
@Override
public JMenuItem getPopupPresenter() {
JCheckBoxMenuItem debugMenuItem = new JCheckBoxMenuItem(this);
debugMenuItem.setSelected(KarmaPreferences.isDebug(project));
return debugMenuItem;
}
}
return new DebugAction(actionContext);
}
}
private static final class ActiveKarmaConfigAction extends CallableSystemAction implements ContextAwareAction {
public ActiveKarmaConfigAction() {
}
@NbBundle.Messages("ActiveKarmaConfigAction.name=Set Configuration")
@Override
public String getName() {
return Bundle.ActiveKarmaConfigAction_name();
}
@Override
public void performAction() {
assert false;
}
@Override
public HelpCtx getHelpCtx() {
return HelpCtx.DEFAULT_HELP;
}
@Override
public Action createContextAwareInstance(final Lookup actionContext) {
class ActiveConfigAction extends AbstractAction implements Presenter.Popup {
@Override
public void actionPerformed(ActionEvent e) {
assert false;
}
@Override
public JMenuItem getPopupPresenter() {
return ConfigMenu.create(actionContext);
}
}
return new ActiveConfigAction();
}
}
private static final class ConfigMenu extends JMenu implements DynamicMenuContent, ActionListener {
final Project project;
private ConfigMenu(Lookup actionContext) {
assert actionContext != null;
project = actionContext.lookup(Project.class);
assert project != null : "Project expected in lookup: " + actionContext;
}
public static ConfigMenu create(Lookup actionContext) {
ConfigMenu configMenu = new ConfigMenu(actionContext);
Mnemonics.setLocalizedText(configMenu, Bundle.ActiveKarmaConfigAction_name());
return configMenu;
}
@Override
public JComponent[] getMenuPresenters() {
removeAll();
// #238803
File configDir = KarmaUtils.getKarmaConfigDir(project);
List<File> configs = KarmaUtils.findKarmaConfigs(configDir);
if (configs.isEmpty()) {
configs = KarmaUtils.findJsFiles(configDir);
}
configs = FileUtils.sortFiles(configs);
if (!configs.isEmpty()) {
String activeConfig = KarmaPreferences.getConfig(project);
for (final File config : configs) {
boolean selected = config.getAbsolutePath().equals(activeConfig);
JRadioButtonMenuItem configItem = new JRadioButtonMenuItem(config.getName(), selected);
configItem.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
KarmaPreferences.setConfig(project, config.getAbsolutePath());
}
});
add(configItem);
}
} else {
setEnabled(false);
}
return new JComponent[] {this};
}
@Override
public JComponent[] synchMenuPresenters(JComponent[] items) {
// always rebuild submenu
return getMenuPresenters();
}
@Override
public void actionPerformed(ActionEvent e) {
assert false;
}
}
private static final class CustomizeKarmaAction extends BaseNodeAction {
public CustomizeKarmaAction() {
}
@Override
protected void performAction(Project project) {
project.getLookup().lookup(CustomizerProvider2.class).showCustomizer(JsTestingProviders.CUSTOMIZER_IDENT, null);
}
@Override
protected boolean enable(Project project) {
return true;
}
@NbBundle.Messages("CustomizeKarmaAction.name=Properties")
@Override
public String getName() {
return Bundle.CustomizeKarmaAction_name();
}
}
private abstract static class BaseNodeAction extends NodeAction {
protected abstract void performAction(Project project);
protected abstract boolean enable(Project project);
@Override
protected final void performAction(Node[] activatedNodes) {
Project project = getProject(activatedNodes);
if (project == null) {
LOGGER.fine("No project found -> no karma action performed");
return;
}
performAction(project);
}
@Override
protected final boolean enable(Node[] activatedNodes) {
Project project = getProject(activatedNodes);
if (project == null) {
LOGGER.fine("No project found -> no karma action enabled");
return false;
}
return enable(project);
}
@Override
public HelpCtx getHelpCtx() {
return null;
}
@Override
protected boolean asynchronous() {
return false;
}
@CheckForNull
private Project getProject(Node[] activatedNodes) {
if (activatedNodes.length != 1) {
return null;
}
Node node = activatedNodes[0];
return node.getLookup().lookup(Project.class);
}
}
}