/* Objective: Since Windows 10 File Explorer search seems messed-up on my laptop (and computers of at least some others reporting online) since late 2019, try to make something for finding files/folders on my laptop while waiting for a fix! First just look at file/folder names (might include option to match text within files later) v2 10Jan2020 Done in this iteration/version: --Previously, while a search was in progress, the results of any previous search stayed in the display box and the "Find files/folders" button went darker (while unresponsive). This might not be desirable for searches that take longer than a couple of seconds as the user might be unsure what is happening. In retrospect, I realise it's because the result of the displayTextArea.setText(null) statement at the beginning of the button's ActionListner's 'action performed' block was not applied until the whole thing was done. May corrospond to e.g. https://stackoverflow.com/questions/15310862/text-area-not-getting-updated-with-the-button-action-performed-event So added a "Search in progress..." message at the beginning, and wrapped the 'searching' statements in an 'invoke later' to ensure the removal of previous text and message display occur immediately. + On completion of search arranged replacement of the 'progress' message with search results /"No items found" if none. --Also arranged indenendent messages to display absent/inappropriate start-folder-path and target (search term) user input. --(Also renamed and rearranged some variables and methods etc for (I hope) increased clarity) [Too many changes to mark them individually in the code below] Next, might address: --Possible to allow a search to be aborted (while displaying items found up to that point) without closing the program window (& have the button text toggle to a message indicating this option)? */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Font; import java.awt.Insets; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.stream.Stream; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; class FindFileOrFolder_v2 { String missingStartMessage = ""; String missingTargetMessage = ""; int walkDepth = 1; // --specifies how many subdirectory levels to go down when collecting files to access // initialized to default 1 (do not look in subdirectories) // GUI components... JLabel startFolderJLabel = new JLabel(" Enter the path of the folder from within which you want to (start your) search..."); JTextArea startFolderTextArea = new JTextArea(2, 60); // input to specify foolder within which to (start) searching JPanel panelForStartComponents = new JPanel(new BorderLayout()); // to hold the above pair of components JButton depthButton = new JButton("Search in subfolders of the starting folder also"); JLabel targetNameJLabel = new JLabel(" Enter the name or partial name of a file or folder you want to find..."); JTextArea targetNameTextArea = new JTextArea(2, 60); // input to specify file/folder names for which to search JPanel panelForTargetComponents = new JPanel(new BorderLayout()); // to hold the above pair of components JPanel panelForInputs = new JPanel(new BorderLayout()); // to hold panelForStartComponents, depthButton, panelForTargetComponents JButton findButton = new JButton("Find files/folders"); JTextArea displayTextArea = new JTextArea(40, 100); // displays output, i.e. paths for files/folders found JPanel panelForFindButtonAndOutput = new JPanel(new BorderLayout()); // to hold the above pair of components JFrame frame = new JFrame("FindFileOrFolder"); // to hold all above (sub)panels FindFileOrFolder_v2() // constructor, called when main method runs { panelForStartComponents.add(startFolderJLabel, BorderLayout.NORTH); startFolderTextArea.setLineWrap(true); startFolderTextArea.setMargin(new Insets(5, 5, 5, 5)); panelForStartComponents.add(new JScrollPane(startFolderTextArea), BorderLayout.SOUTH); panelForInputs.add(panelForStartComponents, BorderLayout.NORTH); panelForTargetComponents.add(targetNameJLabel, BorderLayout.NORTH); targetNameTextArea.setLineWrap(true); targetNameTextArea.setMargin(new Insets(5, 5, 5, 5)); panelForTargetComponents.add(new JScrollPane(targetNameTextArea), BorderLayout.SOUTH); panelForInputs.add(panelForTargetComponents, BorderLayout.SOUTH); findButton.addActionListener(actionEvent -> { // (the 'actionPerformed' method body of the implicit ActionListner...) String startText = startFolderTextArea.getText().trim(); Path startPath = Paths.get(startText); // path to folder within which to (start) searching boolean startPathValid = Files.isDirectory(startPath); // (unfortunately(?), empty string arg seems to // generate path regarded as valid (root/current folder?), so for now adding check re startText.isEmpty() below) final String target = targetNameTextArea.getText(); // (partial) names of files/folders for which to search if (startText.isEmpty() || !startPathValid) { missingStartMessage = "No valid start path supplied" + "\n"; } if (target.isEmpty()) { missingTargetMessage = "No search term supplied" + "\n"; } if (!startText.isEmpty() && startPathValid && !target.isEmpty()) { // only run process below if target text and valid start path have been supplied (avoid wasteful processing) // first, clear any previous message or search results and display a message to say the search is in progress displayTextArea.setForeground(Color.GREEN); // System.out.println(displayTextArea.getFont()); // Temporary, to see original font characteristics // javax.swing.plaf.FontUIResource[family=Dialog,name=Dialog,style=plain,size=12] displayTextArea.setFont(new Font("SERIF", Font.BOLD, 20)); // Minor cosmetic tweak to increase message visibility // interesting glitch: when depth button toggled Find button now disappears // unless font reverted, as desired anyway after the search is done below displayTextArea.setText("Search in progress..."); SwingUtilities.invokeLater(() -> // (want the above progress' message to appear first if search takes a while) { displayTextArea.setForeground(Color.BLACK); displayTextArea.setFont(null);// this seems to work to restore the font to default (as desired) // and prevent the bug described above which the initial setFont(...) call above seems to introduce // Also, as I thought might be the case, the following works too... // displayTextArea.setFont(new Font("DEFAULT", Font.PLAIN, 12)); displayTextArea.setText(null); // clear any previous text before displaying the results try { Stream targetStream = Files.find(startPath, walkDepth, (p,a) -> !isHiddenHandler(p) && // to exclude hidden (temporary etc) files p.getFileName().toString().contains(target)); targetStream.forEach(p -> displayTextArea.append(p.toString() + "\n")); // print paths found in output/display area if (displayTextArea.getText().equals("")) { displayTextArea.setForeground(Color.BLUE); displayTextArea.setText("No results found"); } // (seems inelegant, but have not thought of a way to do directly from the stream code yet) } catch (IOException ex) { if (ex.getClass().getName().equals("java.nio.file.NoSuchFileException")) { missingInputsMessage(); } // (probably not needed now, though, as should not get to try clause without valid start path) } } ); } else { missingInputsMessage(); } } ); findButton.setMargin(new Insets(10, 10, 10, 10)); findButton.setFont(new Font("SansSerif", Font.BOLD, 20)); panelForFindButtonAndOutput.add(findButton, BorderLayout.NORTH); depthButton.addActionListener(actionEvent -> { if (walkDepth == 1) { walkDepth = Integer.MAX_VALUE; depthButton.setText("Search in the starting folder only"); } else // (walkDepth is Integer.MAX_VALUE) { walkDepth = 1; depthButton.setText("Search in subfolders of the starting folder also"); } } ); // toggles walk dept between no-subfolders and all-subfolders panelForInputs.add(depthButton, BorderLayout.CENTER); displayTextArea.setEditable(false); displayTextArea.setLineWrap(true); displayTextArea.setMargin(new Insets(5, 5, 5, 5)); panelForFindButtonAndOutput.add(new JScrollPane(displayTextArea), BorderLayout.SOUTH); frame.add(panelForInputs, BorderLayout.NORTH); frame.add(panelForFindButtonAndOutput, BorderLayout.SOUTH); frame.setResizable(false); // (buttons disappear if user drags frame bottom up, // while if it's dragged down, the extra space just appears as a gap between the panels, // so just hard-coding big displayTextArea for the moment; // looking briefly online, see descriptions/code for how to make rezizable by dragging, but not trivial frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.pack(); // (may need to keep this positioned last) } void missingInputsMessage() // puts message in display area if one or both user inputs missing/incorrect { displayTextArea.setForeground(Color.red); displayTextArea.setText(missingStartMessage + missingTargetMessage); missingStartMessage = ""; // reset for future clicks missingTargetMessage = ""; // (ditto) } boolean isHiddenHandler (Path p) // as Files.isHidden(...) throws checked exception... { // ...easier to handle it externally than in stream coded in findButton's addActionListener above boolean result = false; try { result = Files.isHidden(p); } catch (IOException ex) { System.out.println(ex); } return result; } public static void main(String[] args) { new FindFileOrFolder_v2(); } }