Package org.jetbrains.appcode.reveal

Source Code of org.jetbrains.appcode.reveal.RevealRunConfigurationExtension$MyEditor

package org.jetbrains.appcode.reveal;

import com.intellij.CommonBundle;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.configurations.RunnerSettings;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.ui.ExecutionConsole;
import com.intellij.icons.AllIcons;
import com.intellij.internal.statistic.UsageTrigger;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.ui.HyperlinkLabel;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UIUtil;
import com.jetbrains.cidr.execution.*;
import com.jetbrains.cidr.execution.deviceSupport.AMDevice;
import com.jetbrains.cidr.execution.deviceSupport.AMDeviceUtil;
import com.jetbrains.cidr.xcode.frameworks.AppleSdk;
import com.jetbrains.cidr.xcode.model.*;
import com.jetbrains.cidr.xcode.plist.Plist;
import com.jetbrains.cidr.xcode.plist.PlistDriver;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.io.File;
import java.io.IOException;
import java.util.List;

public class RevealRunConfigurationExtension extends AppCodeRunConfigurationExtension {
  private static final String REVEAL_SETTINGS_TAG = "REVEAL_SETTINGS";
  private static final Key<RevealSettings> REVEAL_SETTINGS_KEY = Key.create(REVEAL_SETTINGS_TAG);
  private static final Key<String> BUNDLE_ID_KEY = Key.create("BUNDLE_INFO");

  @NotNull
  public static RevealSettings getRevealSettings(@NotNull AppCodeRunConfiguration config) {
    RevealSettings settings = config.getCopyableUserData(REVEAL_SETTINGS_KEY);
    return settings == null ? new RevealSettings() : settings;
  }

  public static void setRevealSettings(@NotNull AppCodeRunConfiguration runConfiguration, @Nullable RevealSettings settings) {
    runConfiguration.putCopyableUserData(REVEAL_SETTINGS_KEY, settings);
  }

  @Override
  protected void readExternal(@NotNull AppCodeRunConfiguration runConfiguration, @NotNull Element element)
    throws InvalidDataException {
    Element settingsTag = element.getChild(REVEAL_SETTINGS_TAG);
    RevealSettings settings = null;
    if (settingsTag != null) {
      settings = new RevealSettings();
      settings.autoInject = getAttributeValue(settingsTag.getAttributeValue("autoInject"), settings.autoInject);
      settings.autoInstall = getAttributeValue(settingsTag.getAttributeValue("autoInstall"), settings.autoInstall);
      settings.askToEnableAutoInstall =
        getAttributeValue(settingsTag.getAttributeValue("askToEnableAutoInstall"), settings.askToEnableAutoInstall);
    }
    setRevealSettings(runConfiguration, settings);
  }

  private static boolean getAttributeValue(@Nullable String value, boolean defaultValue) {
    return value == null ? defaultValue : "true".equals(value);
  }

  @Override
  protected void writeExternal(@NotNull AppCodeRunConfiguration runConfiguration, @NotNull Element element)
    throws WriteExternalException {
    RevealSettings settings = getRevealSettings(runConfiguration);

    Element settingsTag = new Element(REVEAL_SETTINGS_TAG);
    settingsTag.setAttribute("autoInject", String.valueOf(settings.autoInject));
    settingsTag.setAttribute("autoInstall", String.valueOf(settings.autoInstall));
    settingsTag.setAttribute("askToEnableAutoInstall", String.valueOf(settings.askToEnableAutoInstall));
    element.addContent(settingsTag);
  }

  @Nullable
  @Override
  protected <P extends AppCodeRunConfiguration> SettingsEditor<P> createEditor(@NotNull P configuration) {
    return new MyEditor<P>();
  }

  @Nullable
  @Override
  protected String getEditorTitle() {
    return "Reveal";
  }

  @Override
  protected boolean isApplicableFor(@NotNull AppCodeRunConfiguration configuration) {
    if (ApplicationManager.getApplication().isUnitTestMode()) return false;
    return configuration instanceof AppCodeAppRunConfiguration;
  }

  @Override
  protected boolean isEnabledFor(@NotNull AppCodeRunConfiguration config, @Nullable RunnerSettings runnerSettings) {
    if (Reveal.getRevealLib() == null) return false;
    return isAvailableForPlatform(config);
  }

  private static boolean isAvailableForPlatform(@NotNull final AppCodeRunConfiguration config) {
    return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
      @Override
      public Boolean compute() {
        XCBuildConfiguration configuration = config.getConfiguration();
        AppleSdk sdk = configuration == null ? null : configuration.getBaseSdk();
        return sdk != null && sdk.getPlatform().isIOS();
      }
    });
  }

  @Override
  public void createAdditionalActions(@NotNull AppCodeRunConfiguration configuration,
                                      @NotNull File product,
                                      @NotNull ExecutionEnvironment environment,
                                      @NotNull BuildConfiguration buildConfiguration,
                                      @NotNull ExecutionConsole console,
                                      @NotNull ProcessHandler processHandler,
                                      @NotNull List<AnAction> actions) throws ExecutionException {
    super.createAdditionalActions(configuration, product, environment, buildConfiguration, console, processHandler, actions);

    actions.add(new RefreshRevealAction(configuration,
                                        environment,
                                        processHandler,
                                        buildConfiguration.getDestination(),
                                        getBundleID(environment, product)));
  }

  @Override
  public void install(@NotNull AppCodeRunConfiguration configuration,
                      @NotNull File product,
                      @NotNull ExecutionEnvironment environment,
                      @NotNull BuildConfiguration buildConfiguration,
                      @NotNull File mainExecutable,
                      @NotNull GeneralCommandLine commandLine) throws ExecutionException {
    super.install(configuration, product, environment, buildConfiguration, mainExecutable, commandLine);

    if (!Reveal.isCompatible()) return;

    RevealSettings settings = getRevealSettings(configuration);
    if (!settings.autoInject) return;

    File toInject = installReveal(configuration, product, environment, buildConfiguration, commandLine, mainExecutable, settings);
    if (toInject == null) return;
    Reveal.LOG.info("Injecting Reveal lib: " + toInject);

    UsageTrigger.trigger("appcode.reveal.inject");

    CidrExecUtil.appendSearchPath(commandLine.getEnvironment(), EnvParameterNames.DYLD_INSERT_LIBRARIES, toInject.getPath());
  }

  @Nullable
  private static File installReveal(@NotNull final AppCodeRunConfiguration configuration,
                                    @NotNull File product,
                                    @NotNull ExecutionEnvironment environment,
                                    @NotNull BuildConfiguration buildConfiguration,
                                    @NotNull GeneralCommandLine commandLine,
                                    @NotNull File mainExecutable,
                                    @NotNull final RevealSettings settings) throws ExecutionException {
    File libReveal = Reveal.getRevealLib();
    if (libReveal == null || !libReveal.exists()) throw new ExecutionException("Reveal library not found");

    Reveal.LOG.info("Reveal lib found at " + libReveal);

    if (hasBundledRevealLib(buildConfiguration, libReveal)) {
      return new File(commandLine.getExePath(), libReveal.getName());
    }

    BuildDestination destination = buildConfiguration.getDestination();
    if (!destination.isIOSDevice()) {
      return libReveal;
    }

    if (!settings.autoInstall) {
      if (!settings.askToEnableAutoInstall) return null;

      final int[] response = new int[1];

      UIUtil.invokeAndWaitIfNeeded(new Runnable() {
        @Override
        public void run() {
          response[0] = Messages.showYesNoDialog("Project is not configured with Reveal library.<br><br>" +
                                                 "Would you like to enable automatic library upload for this run configuration?",
                                                 "Reveal",
                                                 Messages.YES_BUTTON,
                                                 Messages.NO_BUTTON,
                                                 Messages.getQuestionIcon(),
                                                 new DialogWrapper.DoNotAskOption() {
                                                   @Override
                                                   public boolean isToBeShown() {
                                                     return true;
                                                   }

                                                   @Override
                                                   public void setToBeShown(boolean value, int exitCode) {
                                                     settings.askToEnableAutoInstall = value;
                                                   }

                                                   @Override
                                                   public boolean canBeHidden() {
                                                     return true;
                                                   }

                                                   @Override
                                                   public boolean shouldSaveOptionsOnCancel() {
                                                     return false;
                                                   }

                                                   @NotNull
                                                   @Override
                                                   public String getDoNotShowMessage() {
                                                     return CommonBundle.message("dialog.options.do.not.show");
                                                   }
                                                 }
          );
        }
      });
      if (response[0] != Messages.YES) return null;

      settings.autoInstall = true;
      settings.askToEnableAutoInstall = true; // is user changes autoInstall in future, ask him/her again
      setRevealSettings(configuration, settings);
    }

    UsageTrigger.trigger("appcode.reveal.installOnDevice");

    AMDevice device = destination.getIOSDeviceSafe();
    return installOnDevice(libReveal, buildConfiguration, mainExecutable, commandLine, device,
                           getBundleID(environment, product));
  }

  private static boolean hasBundledRevealLib(@NotNull final BuildConfiguration buildConfiguration, @NotNull final File libReveal) {
    return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
      @Override
      public Boolean compute() {
        PBXTarget target = buildConfiguration.getConfiguration().getTarget();
        if (target != null) {
          for (PBXBuildFile eachFile : target.getBuildFiles(PBXBuildPhase.Type.RESOURCES)) {
            PBXReference ref = eachFile.getFileRef();
            String name = ref == null ? null : ref.getFileName();
            if (FileUtil.namesEqual(libReveal.getName(), name)) return true;
          }
        }
        return false;
      }
    });
  }

  @NotNull
  private static File installOnDevice(@NotNull File libReveal,
                                      @NotNull final BuildConfiguration buildConfiguration,
                                      @NotNull File mainExecutable,
                                      @NotNull GeneralCommandLine commandLine,
                                      @NotNull AMDevice device,
                                      @NotNull String bundleId) throws ExecutionException {
    File tempDir;
    File libRevealInTempDir;
    try {
      tempDir = FileUtil.createTempDirectory("libReveal", null);
      libRevealInTempDir = new File(tempDir, libReveal.getName());

      FileUtil.copy(libReveal, libRevealInTempDir);
    }
    catch (IOException e) {
      throw new ExecutionException("Cannot create a temporary copy of Reveal library", e);
    }

    try {
      AppCodeInstaller.codesignBinary(buildConfiguration, mainExecutable, tempDir.getAbsolutePath(), libRevealInTempDir.getName());
      AMDeviceUtil.transferPathToApplicationBundle(device, libRevealInTempDir.getParent(), "/tmp", bundleId);
    } finally {
      FileUtil.delete(tempDir);
    }

    String homeDir = AMDeviceUtil.getHomeDirFromAppEnvironment(commandLine);

    return new File(new File(homeDir), "tmp/" + libRevealInTempDir.getParentFile().getName() + "/" + libRevealInTempDir.getName());
  }


  @NotNull
  private static String getBundleID(@NotNull ExecutionEnvironment environment,
                                    @NotNull File product) throws ExecutionException {
    String result = environment.getUserData(BUNDLE_ID_KEY);
    if (result != null) return result;

    File plistFile = new File(product, "Info.plist");
    Plist plist = PlistDriver.readAnyFormatSafe(plistFile);
    if (plist == null) throw new ExecutionException("Info.plist not found at " + plistFile);

    result = plist.getString("CFBundleIdentifier");
    if (result == null) throw new ExecutionException("CFBundleIdentifier not found in " + plistFile);

    environment.putUserData(BUNDLE_ID_KEY, result);
    return result;
  }

  @Override
  protected void patchCommandLine(@NotNull AppCodeRunConfiguration configuration,
                                  @Nullable RunnerSettings runnerSettings,
                                  @NotNull GeneralCommandLine cmdLine,
                                  @NotNull String runnerId) throws ExecutionException {
  }

  private static class MyEditor<T extends AppCodeRunConfiguration> extends SettingsEditor<T> {
    private HyperlinkLabel myRevealNotFoundOrIncompatible;
    private JBLabel myNotAvailable;

    private JBCheckBox myInjectCheckBox;
    private JBLabel myInjectHint;
    private JBCheckBox myInstallCheckBox;
    private JBLabel myInstallHint;

    boolean isFound;
    boolean isAvailable;

    @Override
    protected void resetEditorFrom(AppCodeRunConfiguration s) {
      RevealSettings settings = getRevealSettings(s);

      myInjectCheckBox.setSelected(settings.autoInject);
      myInstallCheckBox.setSelected(settings.autoInstall);

      boolean found = Reveal.getRevealLib() != null;
      boolean compatible = Reveal.isCompatible();

      String notFoundText = null;
      if (!found) {
        notFoundText = "Reveal.app not found. You can install it from ";
      }
      else if (!compatible) {
        notFoundText = "Incompatible version of Reveal.app. You can download the latest one from ";
      }
      if (notFoundText != null) {
        myRevealNotFoundOrIncompatible.setHyperlinkText(notFoundText, "revealapp.com", "");
      }

      isFound = found && compatible;
      isAvailable = isAvailableForPlatform(s);

      updateControls();
    }

    @Override
    protected void applyEditorTo(AppCodeRunConfiguration s) throws ConfigurationException {
      RevealSettings settings = getRevealSettings(s);

      settings.autoInject = myInjectCheckBox.isSelected();
      settings.autoInstall = myInstallCheckBox.isSelected();

      setRevealSettings(s, settings);
    }

    @NotNull
    @Override
    protected JComponent createEditor() {
      FormBuilder builder = new FormBuilder();

      myRevealNotFoundOrIncompatible = new HyperlinkLabel();
      myRevealNotFoundOrIncompatible.setIcon(AllIcons.RunConfigurations.ConfigurationWarning);
      myRevealNotFoundOrIncompatible.setHyperlinkTarget("http://revealapp.com");

      myNotAvailable = new JBLabel("<html>" +
                                   "Reveal integration is only available for iOS applications.<br>" +
                                   "OS X targets are not yet supported.<br>" +
                                   "</html>");

      myInjectCheckBox = new JBCheckBox("Inject Reveal library on launch");
      myInstallCheckBox = new JBCheckBox("Upload Reveal library on the device if necessary");

      myInjectHint = new JBLabel(UIUtil.ComponentStyle.SMALL);
      myInstallHint = new JBLabel(UIUtil.ComponentStyle.SMALL);

      myInjectCheckBox.addItemListener(new ItemListener() {
        @Override
        public void itemStateChanged(ItemEvent e) {
          updateControls();
        }
      });

      builder.addComponent(myNotAvailable);
      builder.addComponent(myInjectCheckBox, UIUtil.DEFAULT_VGAP * 3);
      builder.setIndent(UIUtil.DEFAULT_HGAP * 4);
      builder.addComponent(myInjectHint);
      builder.setIndent(UIUtil.DEFAULT_HGAP);
      builder.addComponent(myInstallCheckBox);
      builder.setIndent(UIUtil.DEFAULT_HGAP * 5);
      builder.addComponent(myInstallHint);

      JPanel controls = builder.getPanel();

      JPanel panel = new JPanel(new BorderLayout());
      panel.add(controls, BorderLayout.NORTH);
      panel.add(Box.createGlue(), BorderLayout.CENTER);
      panel.add(myRevealNotFoundOrIncompatible, BorderLayout.SOUTH);
      return panel;
    }

    private void updateControls() {
      boolean controlsEnabled = isFound && isAvailable;

      myRevealNotFoundOrIncompatible.setVisible(!isFound);
      myNotAvailable.setVisible(!isAvailable);

      updateStatusAndHint(myInjectCheckBox, myInjectHint,
                          controlsEnabled,
                          "Library is injected on launch using DYLD_INSERT_LIBRARIES variable");

      boolean installButtonEnabled = controlsEnabled && myInjectCheckBox.isSelected();
      updateStatusAndHint(myInstallCheckBox, myInstallHint,
                          installButtonEnabled,
                          "It's not necessary to configure the project manually,<br>" +
                          "library is signed and uploaded automatically"
      );
    }

    private static void updateStatusAndHint(JComponent comp, JBLabel label, boolean enabled, String text) {
      comp.setEnabled(enabled);
      label.setEnabled(enabled);
      StringBuilder fontString = new StringBuilder();
      Color color = enabled ? UIUtil.getLabelForeground() : UIUtil.getLabelDisabledForeground();
      if (color != null) {
        fontString.append("<font color=#");
        UIUtil.appendColor(color, fontString);
        fontString.append(">");
      }
      label.setText("<html>" + fontString + text + "</html>");
    }
  }

  public static class RevealSettings {
    public boolean autoInject;
    public boolean autoInstall = true;
    public boolean askToEnableAutoInstall = true;
  }
}
TOP

Related Classes of org.jetbrains.appcode.reveal.RevealRunConfigurationExtension$MyEditor

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.