private void saveButtonActionPerformed(ActionEvent evt) {
    if (cardMap.get(selectedCard) instanceof TraitDetail) {
      if (((TraitDetail) cardMap.get(selectedCard)).save()) {
        CancellableWorker worker =
            new CancellableWorker() {

              @Override
              public Void doInBackground() throws Exception {
                int lasttrait = abcPanel.decompiledTextArea.lastTraitIndex;
                abcPanel.decompiledTextArea.reloadClass();
                abcPanel.decompiledTextArea.gotoTrait(lasttrait);
                return null;
              }

              @Override
              protected void done() {
                setEditMode(false);
                View.showMessageDialog(
                    null,
                    AppStrings.translate("message.trait.saved"),
                    AppStrings.translate("dialog.message.title"),
                    JOptionPane.INFORMATION_MESSAGE,
                    Configuration.showTraitSavedMessage);
              }
            };
        worker.execute();
      }
    }
  }
  public List<File> exportAS2Scripts(
      AbortRetryIgnoreHandler handler,
      String outdir,
      Map<String, ASMSource> asms,
      ScriptExportSettings exportSettings,
      boolean parallel,
      EventListener evl)
      throws IOException {
    List<File> ret = new ArrayList<>();
    if (!outdir.endsWith(File.separator)) {
      outdir += File.separator;
    }

    Map<String, List<String>> existingNamesMap = new HashMap<>();
    int cnt = 1;
    List<ExportScriptTask> tasks = new ArrayList<>();
    String[] keys = asms.keySet().toArray(new String[asms.size()]);
    Arrays.sort(keys);
    for (String key : keys) {
      ASMSource asm = asms.get(key);
      String currentOutDir = outdir + key + File.separator;
      currentOutDir = new File(currentOutDir).getParentFile().toString() + File.separator;

      List<String> existingNames = existingNamesMap.get(currentOutDir);
      if (existingNames == null) {
        existingNames = new ArrayList<>();
        existingNamesMap.put(currentOutDir, existingNames);
      }

      String name = Helper.makeFileName(asm.getExportFileName());
      int i = 1;
      String baseName = name;
      while (existingNames.contains(name)) {
        i++;
        name = baseName + "_" + i;
      }
      existingNames.add(name);

      tasks.add(
          new ExportScriptTask(
              handler, cnt++, asms.size(), name, asm, currentOutDir, exportSettings, evl));
    }

    if (!parallel || tasks.size() < 2) {
      try {
        CancellableWorker.call(
            new Callable<Void>() {
              @Override
              public Void call() throws Exception {
                for (ExportScriptTask task : tasks) {
                  if (Thread.currentThread().isInterrupted()) {
                    throw new InterruptedException();
                  }

                  ret.add(task.call());
                }
                return null;
              }
            },
            Configuration.exportTimeout.get(),
            TimeUnit.SECONDS);
      } catch (TimeoutException ex) {
        logger.log(
            Level.SEVERE,
            Helper.formatTimeToText(Configuration.exportTimeout.get())
                + " ActionScript export limit reached",
            ex);
      } catch (ExecutionException | InterruptedException ex) {
        logger.log(Level.SEVERE, "Error during AS2 export", ex);
      }
    } else {
      ExecutorService executor =
          Executors.newFixedThreadPool(Configuration.getParallelThreadCount());
      List<Future<File>> futureResults = new ArrayList<>();
      for (ExportScriptTask task : tasks) {
        Future<File> future = executor.submit(task);
        futureResults.add(future);
      }

      try {
        executor.shutdown();
        if (!executor.awaitTermination(Configuration.exportTimeout.get(), TimeUnit.SECONDS)) {
          logger.log(
              Level.SEVERE,
              Helper.formatTimeToText(Configuration.exportTimeout.get())
                  + " ActionScript export limit reached");
        }
      } catch (InterruptedException ex) {
      } finally {
        executor.shutdownNow();
      }

      for (int f = 0; f < futureResults.size(); f++) {
        try {
          if (futureResults.get(f).isDone()) {
            ret.add(futureResults.get(f).get());
          }
        } catch (InterruptedException ex) {
        } catch (ExecutionException ex) {
          logger.log(Level.SEVERE, "Error during ABC export", ex);
        }
      }
    }

    return ret;
  }
  public boolean search(final String txt, boolean ignoreCase, boolean regexp) {
    if ((txt != null) && (!txt.isEmpty())) {
      searchPanel.setOptions(ignoreCase, regexp);
      TagTreeModel ttm = (TagTreeModel) mainPanel.tagTree.getModel();
      TreeItem scriptsNode = ttm.getScriptsNode(mainPanel.getCurrentSwf());
      final List<ABCPanelSearchResult> found = new ArrayList<>();
      if (scriptsNode instanceof ClassesListTreeModel) {
        ClassesListTreeModel clModel = (ClassesListTreeModel) scriptsNode;
        List<ScriptPack> allpacks = clModel.getList();
        final Pattern pat =
            regexp
                ? Pattern.compile(
                    txt, ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0)
                : Pattern.compile(
                    Pattern.quote(txt),
                    ignoreCase ? (Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE) : 0);
        int pos = 0;
        for (final ScriptPack pack : allpacks) {
          pos++;
          String workText = AppStrings.translate("work.searching");
          String decAdd = "";
          if (!SWF.isCached(pack)) {
            decAdd = ", " + AppStrings.translate("work.decompiling");
          }

          try {
            CancellableWorker worker =
                new CancellableWorker() {

                  @Override
                  public Void doInBackground() throws Exception {
                    if (pat.matcher(SWF.getCached(pack).text).find()) {
                      ABCPanelSearchResult searchResult = new ABCPanelSearchResult();
                      searchResult.scriptPack = pack;
                      found.add(searchResult);
                    }
                    return null;
                  }
                };
            worker.execute();
            Main.startWork(
                workText
                    + " \""
                    + txt
                    + "\""
                    + decAdd
                    + " - ("
                    + pos
                    + "/"
                    + allpacks.size()
                    + ") "
                    + pack.getClassPath().toString()
                    + "... ",
                worker);
            worker.get();
          } catch (InterruptedException ex) {
            break;
          } catch (ExecutionException ex) {
            Logger.getLogger(ABCPanel.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      }

      Main.stopWork();

      searchPanel.setSearchText(txt);

      View.execInEventDispatch(
          () -> {
            SearchResultsDialog<ABCPanelSearchResult> sr =
                new SearchResultsDialog<>(
                    ABCPanel.this.mainPanel.getMainFrame().getWindow(), txt, ABCPanel.this);
            sr.setResults(found);
            sr.setVisible(true);
          });

      return true;

      // return searchPanel.setResults(found);
    }
    return false;
  }