history = new HashSet<>();
+ int tick = 0;
+ while (ent.hasMoreElements() && !cancel) {
+ bar.setValue(++tick);
+ JarEntry entry = ent.nextElement();
+ if (!mass.contains(entry.getName()))
+ continue;
+ label.setText("Extracting: " + entry.getName());
+ bar.setVisible(true);
+ if (entry.getName().endsWith(".class")) {
+ JarEntry etn = new JarEntry(entry.getName().replace(".class", ".java"));
+ label.setText("Extracting: " + etn.getName());
+ System.out.println("[SaveAll]: " + etn.getName() + " -> " + outFile.getName());
+
+ if (history.add(etn.getName())) {
+ out.putNextEntry(etn);
+ try {
+ boolean isUnicodeEnabled = decompilationOptions.getSettings().isUnicodeOutputEnabled();
+ String internalName = StringUtilities.removeRight(entry.getName(), ".class");
+ TypeReference type = metadataSystem.lookupType(internalName);
+ TypeDefinition resolvedType;
+ if ((type == null) || ((resolvedType = type.resolve()) == null)) {
+ throw new Exception("Unable to resolve type.");
+ }
+ Writer writer = isUnicodeEnabled ? new OutputStreamWriter(out, StandardCharsets.UTF_8)
+ : new OutputStreamWriter(out);
+ PlainTextOutput plainTextOutput = new PlainTextOutput(writer);
+ plainTextOutput.setUnicodeOutputEnabled(isUnicodeEnabled);
+ settings.getLanguage().decompileType(resolvedType, plainTextOutput, decompilationOptions);
+ writer.flush();
+ } catch (Exception e) {
+ label.setText("Cannot decompile file: " + entry.getName());
+ Luyten.showExceptionDialog("Unable to Decompile file!\nSkipping file...", e);
+ } finally {
+ out.closeEntry();
+ }
+ }
+ } else {
+ try {
+ JarEntry etn = new JarEntry(entry.getName());
+ if (entry.getName().endsWith(".java"))
+ etn = new JarEntry(entry.getName().replace(".java", ".src.java"));
+ if (history.add(etn.getName())) {
+ out.putNextEntry(etn);
+ try {
+ InputStream in = jfile.getInputStream(etn);
+ if (in != null) {
+ try {
+ int count;
+ while ((count = in.read(data, 0, 1024)) != -1) {
+ out.write(data, 0, count);
+ }
+ } finally {
+ in.close();
+ }
+ }
+ } finally {
+ out.closeEntry();
+ }
+ }
+ } catch (ZipException ze) {
+ if (!ze.getMessage().contains("duplicate")) {
+ throw ze;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void doSaveClassDecompiled(File inFile, File outFile) throws Exception {
+ DecompilerSettings settings = cloneSettings();
+ LuytenTypeLoader typeLoader = new LuytenTypeLoader();
+ MetadataSystem metadataSystem = new MetadataSystem(typeLoader);
+ TypeReference type = metadataSystem.lookupType(inFile.getCanonicalPath());
+
+ DecompilationOptions decompilationOptions = new DecompilationOptions();
+ decompilationOptions.setSettings(settings);
+ decompilationOptions.setFullDecompilation(true);
+
+ boolean isUnicodeEnabled = decompilationOptions.getSettings().isUnicodeOutputEnabled();
+ TypeDefinition resolvedType;
+ if (type == null || ((resolvedType = type.resolve()) == null)) {
+ throw new Exception("Unable to resolve type.");
+ }
+ StringWriter stringwriter = new StringWriter();
+ PlainTextOutput plainTextOutput = new PlainTextOutput(stringwriter);
+ plainTextOutput.setUnicodeOutputEnabled(isUnicodeEnabled);
+ settings.getLanguage().decompileType(resolvedType, plainTextOutput, decompilationOptions);
+ String decompiledSource = stringwriter.toString();
+
+ System.out.println("[SaveAll]: " + inFile.getName() + " -> " + outFile.getName());
+ try (FileOutputStream fos = new FileOutputStream(outFile);
+ OutputStreamWriter writer = isUnicodeEnabled ? new OutputStreamWriter(fos, StandardCharsets.UTF_8)
+ : new OutputStreamWriter(fos);
+ BufferedWriter bw = new BufferedWriter(writer)) {
+ bw.write(decompiledSource);
+ bw.flush();
+ }
+ }
+
+ private void doSaveUnknownFile(File inFile, File outFile) throws Exception {
+ try (FileInputStream in = new FileInputStream(inFile); FileOutputStream out = new FileOutputStream(outFile)) {
+ System.out.println("[SaveAll]: " + inFile.getName() + " -> " + outFile.getName());
+
+ byte[] data = new byte[1024];
+ int count;
+ while ((count = in.read(data, 0, 1024)) != -1) {
+ out.write(data, 0, count);
+ }
+ }
+ }
+
+ private DecompilerSettings cloneSettings() {
+ DecompilerSettings settings = ConfigSaver.getLoadedInstance().getDecompilerSettings();
+ DecompilerSettings newSettings = new DecompilerSettings();
+ if (newSettings.getJavaFormattingOptions() == null) {
+ newSettings.setJavaFormattingOptions(JavaFormattingOptions.createDefault());
+ }
+ // synchronized: against main menu changes
+ synchronized (settings) {
+ newSettings.setExcludeNestedTypes(settings.getExcludeNestedTypes());
+ newSettings.setFlattenSwitchBlocks(settings.getFlattenSwitchBlocks());
+ newSettings.setForceExplicitImports(settings.getForceExplicitImports());
+ newSettings.setForceExplicitTypeArguments(settings.getForceExplicitTypeArguments());
+ newSettings.setOutputFileHeaderText(settings.getOutputFileHeaderText());
+ newSettings.setLanguage(settings.getLanguage());
+ newSettings.setShowSyntheticMembers(settings.getShowSyntheticMembers());
+ newSettings.setAlwaysGenerateExceptionVariableForCatchBlocks(
+ settings.getAlwaysGenerateExceptionVariableForCatchBlocks());
+ newSettings.setOutputDirectory(settings.getOutputDirectory());
+ newSettings.setRetainRedundantCasts(settings.getRetainRedundantCasts());
+ newSettings.setIncludeErrorDiagnostics(settings.getIncludeErrorDiagnostics());
+ newSettings.setIncludeLineNumbersInBytecode(settings.getIncludeLineNumbersInBytecode());
+ newSettings.setRetainPointlessSwitches(settings.getRetainPointlessSwitches());
+ newSettings.setUnicodeOutputEnabled(settings.isUnicodeOutputEnabled());
+ newSettings.setMergeVariables(settings.getMergeVariables());
+ newSettings.setShowDebugLineNumbers(settings.getShowDebugLineNumbers());
+ }
+ return newSettings;
+ }
+
+ public boolean isCancel() {
+ return cancel;
+ }
+
+ public void setCancel(boolean cancel) {
+ this.cancel = cancel;
+ }
+
+ public boolean isExtracting() {
+ return extracting;
+ }
+
+ public void setExtracting(boolean extracting) {
+ this.extracting = extracting;
+ }
+
+ public static String getTime(long time) {
+ long lap = System.currentTimeMillis() - time;
+ lap = lap / 1000;
+ StringBuilder sb = new StringBuilder();
+ long hour = ((lap / 60) / 60);
+ long min = ((lap - (hour * 60 * 60)) / 60);
+ long sec = ((lap - (hour * 60 * 60) - (min * 60)));
+ if (hour > 0)
+ sb.append("Hour:").append(hour).append(" ");
+ sb.append("Min(s): ").append(min).append(" Sec: ").append(sec);
+ return sb.toString();
+ }
+
+}
diff --git a/src/main/java/us/deathmarine/luyten/FileUtil.java b/src/main/java/us/deathmarine/luyten/FileUtil.java
new file mode 100644
index 00000000..ac12275e
--- /dev/null
+++ b/src/main/java/us/deathmarine/luyten/FileUtil.java
@@ -0,0 +1,33 @@
+package us.deathmarine.luyten;
+
+import java.io.File;
+import org.fife.ui.rsyntaxtextarea.FileTypeUtil;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rsyntaxtextarea.SyntaxConstants;
+
+public final class FileUtil {
+
+ private static final FileTypeUtil FILE_TYPE_UTIL = FileTypeUtil.get();
+
+ private FileUtil() {
+ throw new UnsupportedOperationException();
+ }
+
+ public static String getLanguage(String fileName) {
+ // Hard code .class -> Java mapping
+ if (fileName.toLowerCase().endsWith(".class")) {
+ return SyntaxConstants.SYNTAX_STYLE_JAVA;
+ }
+ // Otherwise, let RSTA do the job
+ return FILE_TYPE_UTIL.guessContentType(new File(fileName));
+ }
+
+ public static void setLanguage(RSyntaxTextArea area, String fileName) {
+ String type = getLanguage(fileName);
+ if (type == null || type.equals(SyntaxConstants.SYNTAX_STYLE_NONE)) {
+ type = FILE_TYPE_UTIL.guessContentType(area);
+ }
+ area.setSyntaxEditingStyle(type);
+ }
+
+}
diff --git a/src/main/java/us/deathmarine/luyten/FindAllBox.java b/src/main/java/us/deathmarine/luyten/FindAllBox.java
new file mode 100644
index 00000000..8ba55251
--- /dev/null
+++ b/src/main/java/us/deathmarine/luyten/FindAllBox.java
@@ -0,0 +1,407 @@
+package us.deathmarine.luyten;
+
+import com.strobel.assembler.metadata.TypeDefinition;
+import com.strobel.assembler.metadata.TypeReference;
+import com.strobel.core.StringUtilities;
+import com.strobel.decompiler.DecompilationOptions;
+import com.strobel.decompiler.DecompilerSettings;
+import com.strobel.decompiler.PlainTextOutput;
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.regex.Pattern;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.DefaultListModel;
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JProgressBar;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingConstants;
+
+/**
+ * this is the Find All Dialog
+ *
+ * Change with 1.1
+ * Adjust the find all box width
+ *
+ *
+ * @author clevertension
+ * @version 1.1
+ */
+public class FindAllBox extends JDialog {
+
+ private static final long serialVersionUID = -4125409760166690462L;
+ private static final int MIN_WIDTH = 640;
+ private boolean searching;
+
+ private final JButton findButton;
+ private final JTextField textField;
+ private final JCheckBox mcase;
+ private final JCheckBox regex;
+ private final JCheckBox wholew;
+ private final JCheckBox classname;
+ private final JList list;
+ private final JProgressBar progressBar;
+ boolean locked;
+
+ private final JLabel statusLabel = new JLabel("");
+
+ private final DefaultListModel classesList = new DefaultListModel<>();
+
+ private Thread tmp_thread;
+
+ private final MainWindow mainWindow;
+
+ public FindAllBox(final MainWindow mainWindow) {
+ this.setDefaultCloseOperation(HIDE_ON_CLOSE);
+ this.setHideOnEscapeButton();
+
+ progressBar = new JProgressBar(0, 100);
+ this.mainWindow = mainWindow;
+
+ JLabel label = new JLabel("Find What:");
+ textField = new JTextField();
+ findButton = new JButton("Find");
+ findButton.addActionListener(new FindButton());
+
+ mcase = new JCheckBox("Match Case");
+ regex = new JCheckBox("Regex");
+ wholew = new JCheckBox("Whole Words");
+ classname = new JCheckBox("Classnames");
+
+ this.getRootPane().setDefaultButton(findButton);
+
+ list = new JList<>(classesList);
+ list.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
+ list.setLayoutOrientation(JList.VERTICAL_WRAP);
+ list.setVisibleRowCount(-1);
+ list.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent evt) {
+ @SuppressWarnings("unchecked")
+ JList list = (JList) evt.getSource();
+ if (evt.getClickCount() == 2) {
+ int index = list.locationToIndex(evt.getPoint());
+ String entryName = list.getModel().getElementAt(index);
+ String[] array = entryName.split("/");
+ if (entryName.toLowerCase().endsWith(".class")) {
+ String internalName = StringUtilities.removeRight(entryName, ".class");
+ TypeReference type = mainWindow.getSelectedModel().getMetadataSystem().lookupType(internalName);
+ try {
+ mainWindow.getSelectedModel().extractClassToTextPane(type, array[array.length - 1],
+ entryName,
+ null);
+ } catch (Exception ignored) {
+ for (Model m : mainWindow.getModels()) {
+ try {
+ m.extractClassToTextPane(type, array[array.length - 1], entryName, null);
+ } catch (Exception ignored1) {
+ }
+ }
+ }
+
+ } else {
+ try {
+ JarFile jfile = new JarFile(mainWindow.getSelectedModel().getOpenedFile());
+ mainWindow.getSelectedModel().extractSimpleFileEntryToTextPane(
+ jfile.getInputStream(jfile.getEntry(entryName)), array[array.length - 1],
+ entryName);
+ jfile.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ }
+ }
+ });
+ list.setLayoutOrientation(JList.VERTICAL);
+ JScrollPane listScroller = new JScrollPane(list, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
+ JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
+
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ int width = (int) (screenSize.width * 0.35);
+ if (width < MIN_WIDTH) {
+ width = MIN_WIDTH;
+ }
+ final Dimension center = new Dimension(width, 500);
+ final int x = (int) (center.width * 0.2);
+ final int y = (int) (center.height * 0.2);
+ this.setBounds(x, y, center.width, center.height);
+ this.setResizable(false);
+
+ GroupLayout layout = new GroupLayout(getRootPane());
+ getRootPane().setLayout(layout);
+ layout.setAutoCreateGaps(true);
+ layout.setAutoCreateContainerGaps(true);
+
+ layout.setHorizontalGroup(
+ layout.createSequentialGroup().addComponent(label)
+ .addGroup(
+ layout.createParallelGroup(Alignment.LEADING).addComponent(statusLabel)
+ .addComponent(textField)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.LEADING)
+ .addComponent(mcase))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(wholew))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(regex))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(classname)))
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(listScroller)
+ .addComponent(progressBar))))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(findButton))
+
+ );
+
+ layout.linkSize(SwingConstants.HORIZONTAL, findButton);
+ layout.setVerticalGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(label).addComponent(textField)
+ .addComponent(findButton))
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(mcase).addComponent(wholew)
+ .addComponent(regex).addComponent(classname))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(listScroller))))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING)).addComponent(statusLabel)
+ .addComponent(progressBar));
+ this.adjustWindowPositionBySavedState();
+ this.setSaveWindowPositionOnClosing();
+
+ this.setName("Find All");
+ this.setTitle("Find All");
+ }
+
+ private class FindButton extends AbstractAction {
+ private static final long serialVersionUID = 75954129199541874L;
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ tmp_thread = new Thread(() -> {
+ if (findButton.getText().equals("Stop")) {
+ if (tmp_thread != null)
+ tmp_thread.interrupt();
+ setStatus("Stopped.");
+ findButton.setText("Find");
+ locked = false;
+ } else {
+ findButton.setText("Stop");
+ classesList.clear();
+ ConfigSaver configSaver = ConfigSaver.getLoadedInstance();
+ DecompilerSettings settings = configSaver.getDecompilerSettings();
+ File inFile = mainWindow.getSelectedModel().getOpenedFile();
+ boolean filter = ConfigSaver.getLoadedInstance().getLuytenPreferences()
+ .isFilterOutInnerClassEntries();
+ try {
+ JarFile jfile = new JarFile(inFile);
+ Enumeration entLength = jfile.entries();
+ initProgressBar(Collections.list(entLength).size());
+ Enumeration ent = jfile.entries();
+ while (ent.hasMoreElements() && findButton.getText().equals("Stop")) {
+ JarEntry entry = ent.nextElement();
+ String name = entry.getName();
+ setStatus(name);
+ if (filter && name.contains("$"))
+ continue;
+ if (locked || classname.isSelected()) {
+ locked = true;
+ if (search(entry.getName()))
+ addClassName(entry.getName());
+ } else {
+ if (entry.getName().endsWith(".class")) {
+ synchronized (settings) {
+ String internalName = StringUtilities.removeRight(entry.getName(), ".class");
+ TypeReference type;
+ try {
+ type = mainWindow.getSelectedModel().getMetadataSystem().lookupType(internalName);
+ TypeDefinition resolvedType;
+ if (type != null && ((resolvedType = type.resolve()) != null)) {
+ StringWriter stringwriter = new StringWriter();
+ DecompilationOptions decompilationOptions;
+ decompilationOptions = new DecompilationOptions();
+ decompilationOptions.setSettings(settings);
+ decompilationOptions.setFullDecompilation(true);
+ PlainTextOutput plainTextOutput = new PlainTextOutput(stringwriter);
+ plainTextOutput.setUnicodeOutputEnabled(
+ decompilationOptions.getSettings().isUnicodeOutputEnabled());
+ settings.getLanguage().decompileType(resolvedType, plainTextOutput,
+ decompilationOptions);
+ if (search(stringwriter.toString()))
+ addClassName(entry.getName());
+ }
+ } catch (IllegalStateException ise) {
+ if (ise.getMessage().contains("Invalid BootstrapMethods attribute entry: "
+ + "2 additional arguments required for method "
+ + "java/lang/invoke/StringConcatFactory.makeConcatWithConstants, "
+ + "but only 1 specified.")) {
+ // Known issue of Procyon <= 0.5.35 and fix not yet released, refer to
+ // https://web.archive.org/web/20200722211732/https://bitbucket.org/mstrobel/procyon/issues/336/
+ // Searching in a WAR or JAR file could pop-up a lot of error dialogs
+ // for a lot of class files, we simply say nothing here
+ addClassName(entry.getName() + " (search failed due to known "
+ + "Exception in Procyon <= 0.5.35. Opening file will fail "
+ + "too)");
+ } else {
+ // all other IllegalStateException cases
+ addClassName(entry.getName() + " (search failed due to Exception. "
+ + "Opening file will fail too)");
+ Luyten.showExceptionDialog("Caught Exception on: " + entry.getName(),
+ ise);
+ }
+ } catch (Exception e) {
+ addClassName(entry.getName() + " (search failed due to Exception. "
+ + "Opening file will fail too)");
+ Luyten.showExceptionDialog("Caught Exception on: " + entry.getName(), e);
+ }
+ }
+ } else {
+
+ StringBuilder sb = new StringBuilder();
+ double ascii = 0;
+ double other = 0;
+ try (InputStreamReader inputStreamReader = new InputStreamReader(
+ jfile.getInputStream(entry));
+ BufferedReader reader = new BufferedReader(inputStreamReader)) {
+ String line;
+ while ((line = reader.readLine()) != null) {
+ sb.append(line).append("\n");
+ // Source: https://stackoverflow.com/a/13533390/5894824
+ for (byte b : line.getBytes()) {
+ if (b == 0x09 || b == 0x0A || b == 0x0C || b == 0x0D || (b >= 0x20 && b <= 0x7E))
+ ascii++;
+ else other++;
+ }
+ }
+ }
+
+ if ((other == 0 || ascii / (ascii + other) > 0.5) && search(sb.toString()))
+ addClassName(entry.getName());
+ }
+ }
+ }
+ setSearching(false);
+ if (findButton.getText().equals("Stop")) {
+ setStatus("Done.");
+ findButton.setText("Find");
+ locked = false;
+ }
+ jfile.close();
+ locked = false;
+ } catch (Exception e) {
+ Luyten.showExceptionDialog("Exception!", e);
+ }
+
+ }
+ });
+ tmp_thread.start();
+
+ }
+
+ }
+
+ private boolean search(String bulk) {
+ String a = textField.getText();
+ String b = bulk;
+ if (regex.isSelected())
+ return Pattern.matches(a, b);
+ if (wholew.isSelected())
+ a = " " + a + " ";
+ if (!mcase.isSelected()) {
+ a = a.toLowerCase();
+ b = b.toLowerCase();
+ }
+ return b.contains(a);
+ }
+
+ private void setHideOnEscapeButton() {
+ Action escapeAction = new AbstractAction() {
+ private static final long serialVersionUID = 6846566740472934801L;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ FindAllBox.this.setVisible(false);
+ }
+ };
+
+ KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
+ this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
+ this.getRootPane().getActionMap().put("ESCAPE", escapeAction);
+ }
+
+ private void adjustWindowPositionBySavedState() {
+ WindowPosition windowPosition = ConfigSaver.getLoadedInstance().getFindWindowPosition();
+
+ if (windowPosition.isSavedWindowPositionValid()) {
+ this.setLocation(windowPosition.getWindowX(), windowPosition.getWindowY());
+ }
+ }
+
+ private void setSaveWindowPositionOnClosing() {
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowDeactivated(WindowEvent e) {
+ WindowPosition windowPosition = ConfigSaver.getLoadedInstance().getFindWindowPosition();
+ windowPosition.readPositionFromDialog(FindAllBox.this);
+ }
+ });
+ }
+
+ public void showFindBox() {
+ this.setVisible(true);
+ this.textField.requestFocus();
+ }
+
+ public void hideFindBox() {
+ this.setVisible(false);
+ }
+
+ public void setStatus(String text) {
+ if (text.length() > 25) {
+ this.statusLabel.setText("Searching in file: ..." + text.substring(text.length() - 25));
+ } else {
+ this.statusLabel.setText("Searching in file: " + text);
+ }
+
+ progressBar.setValue(progressBar.getValue() + 1);
+ }
+
+ public void addClassName(String className) {
+ this.classesList.addElement(className);
+ }
+
+ public void initProgressBar(Integer length) {
+ progressBar.setMaximum(length);
+ progressBar.setValue(0);
+ progressBar.setStringPainted(true);
+ }
+
+ public boolean isSearching() {
+ return searching;
+ }
+
+ public void setSearching(boolean searching) {
+ this.searching = searching;
+ }
+
+}
diff --git a/src/main/java/us/deathmarine/luyten/FindBox.java b/src/main/java/us/deathmarine/luyten/FindBox.java
new file mode 100644
index 00000000..3b316adb
--- /dev/null
+++ b/src/main/java/us/deathmarine/luyten/FindBox.java
@@ -0,0 +1,234 @@
+package us.deathmarine.luyten;
+
+import java.awt.Dimension;
+import java.awt.Toolkit;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.BorderFactory;
+import javax.swing.GroupLayout;
+import javax.swing.GroupLayout.Alignment;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.SwingConstants;
+import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
+import org.fife.ui.rtextarea.SearchContext;
+import org.fife.ui.rtextarea.SearchEngine;
+
+public class FindBox extends JDialog {
+
+ private static final long serialVersionUID = -4125409760166690462L;
+
+ JCheckBox mcase;
+ JCheckBox regex;
+ JCheckBox wholew;
+ JCheckBox reverse;
+ JCheckBox wrap;
+ private final JButton findButton;
+ JTextField textField;
+ private final MainWindow mainWindow;
+
+ public void showFindBox() {
+ this.setVisible(true);
+ this.textField.requestFocus();
+ this.textField.selectAll();
+ }
+
+ public void hideFindBox() {
+ this.setVisible(false);
+ }
+
+ public FindBox(final MainWindow mainWindow) {
+ this.mainWindow = mainWindow;
+ this.setDefaultCloseOperation(HIDE_ON_CLOSE);
+ this.setHideOnEscapeButton();
+
+ JLabel label = new JLabel("Find What:");
+ textField = new JTextField();
+
+ RSyntaxTextArea pane = mainWindow.getSelectedModel().getCurrentTextArea();
+ if (pane != null) {
+ textField.setText(pane.getSelectedText());
+ }
+ mcase = new JCheckBox("Match Case");
+ regex = new JCheckBox("Regex");
+ wholew = new JCheckBox("Whole Words");
+ reverse = new JCheckBox("Search Backwards");
+ wrap = new JCheckBox("Wrap");
+
+ findButton = new JButton("Find");
+ findButton.addActionListener(new FindButton());
+ this.getRootPane().setDefaultButton(findButton);
+
+ KeyStroke funcF3 = KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0, false);
+ this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(funcF3, "FindNext");
+ this.getRootPane().getActionMap().put("FindNext", new FindExploreAction(true));
+
+ KeyStroke sfuncF3 = KeyStroke.getKeyStroke(KeyEvent.VK_F3, InputEvent.SHIFT_DOWN_MASK, false);
+ this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(sfuncF3, "FindPrevious");
+ this.getRootPane().getActionMap().put("FindPrevious", new FindExploreAction(false));
+
+ mcase.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+ regex.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+ wholew.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+ reverse.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+ wrap.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+
+ Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
+ final Dimension center = new Dimension((int) (screenSize.width * 0.35),
+ Math.min((int) (screenSize.height * 0.20), 200));
+ final int x = (int) (center.width * 0.2);
+ final int y = (int) (center.height * 0.2);
+ this.setBounds(x, y, center.width, center.height);
+ this.setResizable(false);
+
+ GroupLayout layout = new GroupLayout(getRootPane());
+ getRootPane().setLayout(layout);
+ layout.setAutoCreateGaps(true);
+ layout.setAutoCreateContainerGaps(true);
+
+ layout.setHorizontalGroup(layout.createSequentialGroup().addComponent(label)
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(textField)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(mcase)
+ .addComponent(wholew).addComponent(wrap))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(regex)
+ .addComponent(reverse))))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING).addComponent(findButton)));
+
+ layout.linkSize(SwingConstants.HORIZONTAL, findButton);
+ layout.setVerticalGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(label).addComponent(textField)
+ .addComponent(findButton))
+ .addGroup(layout.createParallelGroup(Alignment.LEADING)
+ .addGroup(layout.createSequentialGroup()
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(mcase)
+ .addComponent(regex))
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(wholew)
+ .addComponent(reverse))
+ .addGroup(layout.createParallelGroup(Alignment.BASELINE).addComponent(wrap)))));
+
+ this.adjustWindowPositionBySavedState();
+ this.setSaveWindowPositionOnClosing();
+
+ this.setName("Find");
+ this.setTitle("Find");
+ this.setVisible(true);
+ }
+
+ private class FindButton extends AbstractAction {
+ private static final long serialVersionUID = 75954129199541874L;
+
+ @Override
+ public void actionPerformed(ActionEvent event) {
+ if (textField.getText().length() == 0)
+ return;
+
+ RSyntaxTextArea pane = mainWindow.getSelectedModel().getCurrentTextArea();
+ if (pane == null)
+ return;
+
+ SearchContext context = new SearchContext();
+ context.setSearchFor(textField.getText());
+ context.setMatchCase(mcase.isSelected());
+ context.setRegularExpression(regex.isSelected());
+ context.setSearchForward(!reverse.isSelected());
+ context.setWholeWord(wholew.isSelected());
+
+ if (!SearchEngine.find(pane, context).wasFound()) {
+ if (wrap.isSelected()) {
+ pane.setSelectionStart(0);
+ pane.setSelectionEnd(0);
+ } else {
+ mainWindow.getLabel().setText("Search Complete");
+ }
+ }
+ }
+
+ }
+
+ private void setHideOnEscapeButton() {
+ Action escapeAction = new AbstractAction() {
+ private static final long serialVersionUID = 5572504000935312338L;
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ FindBox.this.setVisible(false);
+ }
+ };
+
+ KeyStroke escapeKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, false);
+ this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(escapeKeyStroke, "ESCAPE");
+ this.getRootPane().getActionMap().put("ESCAPE", escapeAction);
+ }
+
+ private void adjustWindowPositionBySavedState() {
+ WindowPosition windowPosition = ConfigSaver.getLoadedInstance().getFindWindowPosition();
+
+ if (windowPosition.isSavedWindowPositionValid()) {
+ this.setLocation(windowPosition.getWindowX(), windowPosition.getWindowY());
+ }
+ }
+
+ private void setSaveWindowPositionOnClosing() {
+ this.addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowDeactivated(WindowEvent e) {
+ WindowPosition windowPosition = ConfigSaver.getLoadedInstance().getFindWindowPosition();
+ windowPosition.readPositionFromDialog(FindBox.this);
+ }
+ });
+ }
+
+ public void fireExploreAction(boolean direction) {
+ new FindExploreAction(direction).actionPerformed(null);
+ }
+
+ class FindExploreAction extends AbstractAction {
+ /**
+ *
+ */
+ private static final long serialVersionUID = -4391670062679240573L;
+ boolean direction;
+
+ public FindExploreAction(boolean forward) {
+ direction = forward;
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ if (textField.getText().length() == 0)
+ return;
+ RSyntaxTextArea pane = mainWindow.getSelectedModel().getCurrentTextArea();
+ if (pane == null)
+ return;
+ SearchContext context = new SearchContext();
+ context.setSearchFor(textField.getText());
+ context.setMatchCase(mcase.isSelected());
+ context.setRegularExpression(regex.isSelected());
+ context.setSearchForward(direction);
+ context.setWholeWord(wholew.isSelected());
+
+ if (!SearchEngine.find(pane, context).wasFound()) {
+ if (wrap.isSelected()) {
+ pane.setSelectionStart(0);
+ pane.setSelectionEnd(0);
+ } else {
+ mainWindow.getLabel().setText("Search Complete");
+ }
+ }
+
+ }
+
+ }
+
+}
diff --git a/src/main/java/us/deathmarine/luyten/JFontChooser.java b/src/main/java/us/deathmarine/luyten/JFontChooser.java
new file mode 100644
index 00000000..55786131
--- /dev/null
+++ b/src/main/java/us/deathmarine/luyten/JFontChooser.java
@@ -0,0 +1,755 @@
+package us.deathmarine.luyten;
+
+import java.awt.BorderLayout;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Frame;
+import java.awt.GraphicsEnvironment;
+import java.awt.GridLayout;
+import java.awt.event.ActionEvent;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import javax.swing.AbstractAction;
+import javax.swing.Action;
+import javax.swing.ActionMap;
+import javax.swing.BorderFactory;
+import javax.swing.BoxLayout;
+import javax.swing.DefaultListCellRenderer;
+import javax.swing.InputMap;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JLabel;
+import javax.swing.JList;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.ListSelectionModel;
+import javax.swing.SwingUtilities;
+import javax.swing.border.Border;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.Document;
+import javax.swing.text.JTextComponent;
+import javax.swing.text.Position;
+
+/**
+ * The JFontChooser
class is a swing component for font selection.
+ * This class has JFileChooser
like APIs. The following code pops
+ * up a font chooser dialog.
+ **/
+public class JFontChooser extends JComponent {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 8856126034081661L;
+ // class variables
+ /**
+ * Return value from showDialog()
.
+ *
+ * @see #showDialog
+ **/
+ public static final int OK_OPTION = 0;
+ /**
+ * Return value from showDialog()
.
+ *
+ * @see #showDialog
+ **/
+ public static final int CANCEL_OPTION = 1;
+ /**
+ * Return value from showDialog()
.
+ *
+ * @see #showDialog
+ **/
+ public static final int ERROR_OPTION = -1;
+ private static final Font DEFAULT_SELECTED_FONT = new Font("Serif", Font.PLAIN, 12);
+ private static final Font DEFAULT_FONT = new Font("Dialog", Font.PLAIN, 10);
+ private static final int[] FONT_STYLE_CODES = {Font.PLAIN, Font.BOLD, Font.ITALIC, Font.BOLD | Font.ITALIC};
+ private static final String[] DEFAULT_FONT_SIZE_STRINGS = {"8", "9", "10", "11", "12", "14", "16", "18", "20",
+ "22", "24", "26", "28", "36", "48", "72",};
+
+ // instance variables
+ protected int dialogResultValue = ERROR_OPTION;
+
+ protected DefaultListCellRenderer defaultRenderer = new DefaultListCellRenderer();
+
+ private String[] fontStyleNames;
+ private String[] fontFamilyNames;
+ private final String[] fontSizeStrings;
+ private JTextField fontFamilyTextField;
+ private JTextField fontStyleTextField;
+ private JTextField fontSizeTextField;
+ private JList> fontNameList;
+ private JList> fontStyleList;
+ private JList> fontSizeList;
+ private JPanel fontNamePanel;
+ private JPanel fontStylePanel;
+ private JPanel fontSizePanel;
+ private JPanel samplePanel;
+ private JTextField sampleText;
+
+ /**
+ * Constructs a JFontChooser
object.
+ **/
+ public JFontChooser() {
+ this(DEFAULT_FONT_SIZE_STRINGS);
+ }
+
+ /**
+ * Constructs a JFontChooser
object using the given font size
+ * array.
+ *
+ * @param fontSizeStrings the array of font size string.
+ **/
+ public JFontChooser(String[] fontSizeStrings) {
+ if (fontSizeStrings == null) {
+ fontSizeStrings = DEFAULT_FONT_SIZE_STRINGS;
+ }
+ this.fontSizeStrings = fontSizeStrings;
+
+ JPanel selectPanel = new JPanel();
+ selectPanel.setLayout(new BoxLayout(selectPanel, BoxLayout.X_AXIS));
+ selectPanel.add(getFontFamilyPanel());
+ selectPanel.add(getFontStylePanel());
+ selectPanel.add(getFontSizePanel());
+
+ JPanel contentsPanel = new JPanel();
+ contentsPanel.setLayout(new GridLayout(2, 1));
+ contentsPanel.add(selectPanel, BorderLayout.NORTH);
+ contentsPanel.add(getSamplePanel(), BorderLayout.CENTER);
+
+ this.setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
+ this.add(contentsPanel);
+ this.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+ this.setSelectedFont(DEFAULT_SELECTED_FONT);
+ }
+
+ public JTextField getFontFamilyTextField() {
+ if (fontFamilyTextField == null) {
+ fontFamilyTextField = new JTextField();
+ fontFamilyTextField.addFocusListener(new TextFieldFocusHandlerForTextSelection(fontFamilyTextField));
+ fontFamilyTextField.addKeyListener(new TextFieldKeyHandlerForListSelectionUpDown(getFontFamilyList()));
+ fontFamilyTextField.getDocument()
+ .addDocumentListener(new ListSearchTextFieldDocumentHandler(getFontFamilyList()));
+ fontFamilyTextField.setFont(DEFAULT_FONT);
+
+ }
+ return fontFamilyTextField;
+ }
+
+ public JTextField getFontStyleTextField() {
+ if (fontStyleTextField == null) {
+ fontStyleTextField = new JTextField();
+ fontStyleTextField.addFocusListener(new TextFieldFocusHandlerForTextSelection(fontStyleTextField));
+ fontStyleTextField.addKeyListener(new TextFieldKeyHandlerForListSelectionUpDown(getFontStyleList()));
+ fontStyleTextField.getDocument()
+ .addDocumentListener(new ListSearchTextFieldDocumentHandler(getFontStyleList()));
+ fontStyleTextField.setFont(DEFAULT_FONT);
+
+ }
+ return fontStyleTextField;
+ }
+
+ public JTextField getFontSizeTextField() {
+ if (fontSizeTextField == null) {
+ fontSizeTextField = new JTextField();
+ fontSizeTextField.addFocusListener(new TextFieldFocusHandlerForTextSelection(fontSizeTextField));
+ fontSizeTextField.addKeyListener(new TextFieldKeyHandlerForListSelectionUpDown(getFontSizeList()));
+ fontSizeTextField.getDocument()
+ .addDocumentListener(new ListSearchTextFieldDocumentHandler(getFontSizeList()));
+ fontSizeTextField.setFont(DEFAULT_FONT);
+ }
+ return fontSizeTextField;
+ }
+
+ public JList> getFontFamilyList() {
+ if (fontNameList == null) {
+ fontNameList = new JList