package moviescraper.doctord.view; import java.awt.EventQueue; import javax.swing.BorderFactory; import javax.swing.DefaultListModel; import javax.swing.JFrame; import javax.swing.JMenu; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.SwingUtilities; import java.awt.BorderLayout; import javax.swing.JList; import moviescraper.doctord.controller.SelectFileListAction; import moviescraper.doctord.controller.amalgamation.AllAmalgamationOrderingPreferences; import moviescraper.doctord.model.IconCache; import moviescraper.doctord.model.Movie; import moviescraper.doctord.model.SearchResult; import moviescraper.doctord.model.dataitem.Actor; import moviescraper.doctord.model.preferences.GuiSettings; import moviescraper.doctord.model.preferences.MoviescraperPreferences; import moviescraper.doctord.view.renderer.FileRenderer; import java.awt.Cursor; import java.awt.Desktop; import java.awt.Dimension; import java.awt.SystemColor; import javax.swing.UIManager; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import javafx.embed.swing.JFXPanel; import javafx.stage.DirectoryChooser; import javax.swing.BoxLayout; import javax.swing.event.ListSelectionListener; public class GUIMain { //Objects Used to Keep Track of Program State private List<File> currentlySelectedNfoFileList; private List<File> currentlySelectedPosterFileList; private List<File> currentlySelectedFolderJpgFileList; private List<File> currentlySelectedFanartFileList; private List<File> currentlySelectedTrailerFileList; private List<File> currentlySelectedMovieFileList; private List<File> currentlySelectedActorsFolderList; private File currentlySelectedDirectoryList; private File defaultHomeDirectory; private MoviescraperPreferences preferences; private GuiSettings guiSettings; private AllAmalgamationOrderingPreferences allAmalgamationOrderingPreferences; //scraped movies public List <Movie> movieToWriteToDiskList; //Gui Elements JFrame frmMoviescraper; protected WindowBlocker frmMovieScraperBlocker; private DefaultListModel<File> listModelFiles; private JPanel fileListPanel; private FileDetailPanel fileDetailPanel; private JScrollPane fileListScrollPane; private JSplitPane fileListFileDetailSplitPane; private JList<File> fileList; private DirectoryChooser chooser; private MessageConsolePanel messageConsolePanel; private ProgressMonitor progressMonitor; //variables for fileList private static int CHAR_DELTA = 1000; private String m_key; private long m_time; //Menus GUIMainMenuBar menuBar; JMenu preferenceMenu; private String originalJavLibraryMovieTitleBeforeAmalgamate; //Dimensions of various elements private static final int defaultMainFrameX = 1045; private static final int defaultMainFrameY = 850; private final static boolean debugMessages = false; private GUIMainButtonPanel buttonPanel; //JavaFX stuff //Ignore warnings about this not being used. It is used for the file browser. //You can comment this variable out and you will see the file browsing no longer works :) @SuppressWarnings("unused") private final JFXPanel fxPanel = new JFXPanel(); //ensures the JavaFX library is loaded - allows us to use DirectoryChooser later on /** * Launch the application. */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { @Override public void run() { try { UIManager.setLookAndFeel(UIManager .getSystemLookAndFeelClassName()); //Prevent text area font from looking different than text field font UIManager.getDefaults().put("TextArea.font", UIManager.getFont("TextField.font")); GUIMain window = new GUIMain(); System.out.println("Gui Initialized"); window.frmMoviescraper.setVisible(true); } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, ExceptionUtils.getStackTrace(e),"Unhandled Exception",JOptionPane.ERROR_MESSAGE); } } }); } /** * Create the application. */ public GUIMain() { initialize(); } public void debugWriter(String message) { if(debugMessages) System.out.println(message); } /** * Restore amalgamation preferences from what is saved on disk */ public void reinitializeAmalgamationPreferencesFromFile() { allAmalgamationOrderingPreferences = new AllAmalgamationOrderingPreferences(); allAmalgamationOrderingPreferences = allAmalgamationOrderingPreferences.initializeValuesFromPreferenceFile(); } /** * Initialize the contents of the frame. */ private void initialize() { preferences = MoviescraperPreferences.getInstance(); guiSettings = GuiSettings.getInstance(); reinitializeAmalgamationPreferencesFromFile(); setCurrentlySelectedNfoFileList(new ArrayList<File>()); setCurrentlySelectedMovieFileList(new ArrayList<File>()); setCurrentlySelectedPosterFileList(new ArrayList<File>()); setCurrentlySelectedFolderJpgFileList(new ArrayList<File>()); setCurrentlySelectedFanartFileList(new ArrayList<File>()); setCurrentlySelectedTrailerFileList(new ArrayList<File>()); currentlySelectedActorsFolderList = new ArrayList<>(); movieToWriteToDiskList = new ArrayList<>(); frmMoviescraper = new JFrame(); frmMovieScraperBlocker = new WindowBlocker(); //set up the window that sits above the frame and can block input to this frame if needed while a dialog is open frmMoviescraper.setGlassPane(frmMovieScraperBlocker); frmMoviescraper.setBackground(SystemColor.window); frmMoviescraper.setPreferredSize(new Dimension(defaultMainFrameX, defaultMainFrameY)); frmMoviescraper.setTitle("JAVMovieScraper"); frmMoviescraper.setBounds(100, 100, defaultMainFrameX, defaultMainFrameY); frmMoviescraper.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //create tree view icon provider IconCache.setIconProvider(getGuiSettings().getUseContentBasedTypeIcons() ? IconCache.IconProviderType.CONTENT : IconCache.IconProviderType.SYSTEM); //Used for icon in the title bar frmMoviescraper.setIconImage(GUICommon.getProgramIcon()); //Set up the file list panel - the panel where the user picks what file to scrape setUpFileListPanel(); //Set up the bottom panel - area for message panel messageConsolePanel = new MessageConsolePanel(); frmMoviescraper.getContentPane().add(messageConsolePanel, BorderLayout.SOUTH); buttonPanel = new GUIMainButtonPanel(this); frmMoviescraper.getContentPane().add(buttonPanel, BorderLayout.NORTH); //add in the menu bar menuBar = new GUIMainMenuBar(this); frmMoviescraper.setJMenuBar(menuBar); int gap = 7; fileListFileDetailSplitPane.setBorder(BorderFactory.createEmptyBorder()); fileListFileDetailSplitPane.setDividerSize(gap); messageConsolePanel.setBorder(BorderFactory.createEmptyBorder(gap, 0, 0, 0)); // restore gui state buttonPanel.setVisible(guiSettings.getShowToolbar()); messageConsolePanel.setVisible(guiSettings.getShowOutputPanel()); } /** * @param upIcon * @param browseDirectoryIcon * @param refreshDirectoryIcon */ private void setUpFileListPanel() { fileListPanel = new JPanel(); defaultHomeDirectory = getGuiSettings().getLastUsedDirectory(); setCurrentlySelectedDirectoryList(defaultHomeDirectory); listModelFiles = new DefaultListModel<>(); setFileList(new JList<>(listModelFiles)); //add in a keyListener so that you can start typing letters in the list and it will take you to that item in the list //if you type the second letter within CHAR_DELTA amount of time that will count as the Nth letter of the search //instead of the first getFileList().addKeyListener(new KeyListener() { @Override public void keyTyped(KeyEvent e) { //do nothing until the key is released } @Override public void keyReleased(KeyEvent e) { char ch = e.getKeyChar(); // ignore searches for non alpha-numeric characters if (!Character.isLetterOrDigit(ch)) { return; } // reset string if too much time has elapsed if (m_time + CHAR_DELTA < System.currentTimeMillis()) { m_key = ""; } m_time = System.currentTimeMillis(); m_key += Character.toLowerCase(ch); // Iterate through items in the list until a matching prefix is found. // This technique is fine for small lists, however, doing a linear // search over a very large list with additional string manipulation // (eg: toLowerCase) within the tight loop would be quite slow. // In that case, pre-processing the case-conversions, and storing the // strings in a more search-efficient data structure such as a Trie // or a Ternary Search Tree would lead to much faster find. for (int i = 0; i < getFileList().getModel().getSize(); i++) { String str = getFileList().getModel().getElementAt(i).getName() .toString().toLowerCase(); if (str.startsWith(m_key)) { getFileList().setSelectedIndex(i); // change selected item in list getFileList().ensureIndexIsVisible(i); // change listbox // scroll-position break; } } } @Override public void keyPressed(KeyEvent e) { //do nothing until the key is released } }); //add mouse listener for double click getFileList().addMouseListener(new MouseListener() { @Override public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mousePressed(MouseEvent e) { if(e.getClickCount() >= 2){ @SuppressWarnings("unchecked") JList<File> theList = (JList<File>) e.getSource(); try { File doubleClickedFile = theList.getSelectedValue(); if(doubleClickedFile != null && doubleClickedFile.exists() && doubleClickedFile.isDirectory()) { try{ setCurrentlySelectedDirectoryList(doubleClickedFile); frmMoviescraper.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); updateFileListModel(getCurrentlySelectedDirectoryList(), false); } finally { getGuiSettings().setLastUsedDirectory(getCurrentlySelectedDirectoryList()); frmMoviescraper.setCursor(Cursor.getDefaultCursor()); } } else { Desktop.getDesktop().open(theList.getSelectedValue()); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } @Override public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } }); getFileList().addListSelectionListener(new SelectFileListAction(this)); FileList fl = new FileList(); fileListScrollPane = fl.getGui( showFileListSorted(getCurrentlySelectedDirectoryList()), listModelFiles, true); fileListPanel.setLayout(new BoxLayout(fileListPanel, BoxLayout.Y_AXIS)); fileListPanel.add(fileListScrollPane); fileDetailPanel = new FileDetailPanel(getPreferences(), this); JScrollPane fileDetailsScrollPane = new JScrollPane(fileDetailPanel); fileListFileDetailSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, fileListPanel, fileDetailsScrollPane); fileListPanel.setMinimumSize(new Dimension(200,50)); fileDetailsScrollPane.setMinimumSize(new Dimension(100,50)); frmMoviescraper.getContentPane().add(fileListFileDetailSplitPane, BorderLayout.CENTER); } public void removeOldScrapedMovieReferences() { setOriginalJavLibraryMovieTitleBeforeAmalgamate(null); if(movieToWriteToDiskList != null) movieToWriteToDiskList.clear(); } public void removeOldSelectedFileReferences(){ getCurrentlySelectedNfoFileList().clear(); getCurrentlySelectedMovieFileList().clear(); getCurrentlySelectedActorsFolderList().clear(); getCurrentlySelectedPosterFileList().clear(); getCurrentlySelectedFolderJpgFileList().clear(); getCurrentlySelectedFanartFileList().clear(); getCurrentlySelectedTrailerFileList().clear(); getMovieToWriteToDiskList().clear(); removeOldScrapedMovieReferences(); } public void updateFileListModel(File currentlySelectedDirectory, boolean keepSelectionsAndReferences) { //make sure this happens on the event dispatch thread, since it can be called from, for example, a background thread that is writing the files SwingUtilities.invokeLater(() -> { try{ getFrmMoviescraper().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); File [] filesToList = showFileListSorted(currentlySelectedDirectory); List<File> selectValuesListBeforeUpdate = getFileList().getSelectedValuesList(); //We don't want to fire the listeners events when reselecting the items because this //will cause us additional IO that is not needed as the program rereads the nfo. //To avoid this, we can save out the old listener, remove it, select the items and then add it back ListSelectionListener[] fileListSelectionListener = null; if(keepSelectionsAndReferences) { fileListSelectionListener = getFileList().getListSelectionListeners(); getFileList().removeListSelectionListener(getFileList().getListSelectionListeners()[0]); } listModelFiles.removeAllElements(); for (File file : filesToList) { listModelFiles.addElement(file); } if(!keepSelectionsAndReferences) { removeOldScrapedMovieReferences(); removeOldSelectedFileReferences(); } //select the old values we had before we updated the list for(File currentValueToSelect : selectValuesListBeforeUpdate) { getFileList().setSelectedValue(currentValueToSelect, false); } if(keepSelectionsAndReferences && fileListSelectionListener != null) { getFileList().addListSelectionListener(fileListSelectionListener[0]); } } finally { getFrmMoviescraper().setCursor(Cursor.getDefaultCursor()); } }); } private File[] showFileListSorted(File currentlySelectedDirectory) { File[] sortedList = currentlySelectedDirectory.listFiles(); //Make a comparator so we get alphabetic order, with all directories first, then all the files (Like Windows Explorer) Comparator<File> comp = new Comparator<File>() { @Override public int compare(File file1, File file2) { // Directory before non-directory if (file1.isDirectory() && !file2.isDirectory()) { return -1; } // Non-directory after directory else if (!file1.isDirectory() && file2.isDirectory()) { return 1; } // Alphabetic order otherwise else { return file1.compareTo(file2); } } }; Arrays.sort(sortedList, comp); return sortedList; } public void clearAllFieldsOfFileDetailPanel() { fileDetailPanel.clearView(); fileDetailPanel.setTitleEditable(false); } //Update the File Detail Panel GUI so the user can see what is scraped in public void updateAllFieldsOfFileDetailPanel(boolean forceUpdatePoster, boolean newMovieWasSet) { fileDetailPanel.currentListIndexOfDisplayedMovie = 0; fileDetailPanel.updateView(forceUpdatePoster, newMovieWasSet); } public static SearchResult showOptionPane(SearchResult [] searchResults, String siteName) { if(searchResults.length > 0) { SelectionDialog selectionDialog = new SelectionDialog(searchResults, siteName); int optionPicked = JOptionPane.showOptionDialog(null, selectionDialog, "Select Movie to Scrape From " + siteName, JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, null, null); if(optionPicked == JOptionPane.CANCEL_OPTION) return null; return selectionDialog.getSelectedValue(); } else return null; } class FileList { public JScrollPane getGui(File[] all, DefaultListModel<File> listModelFiles, boolean vertical) { //Gotta clear out the old list before we can populate it with new stuff getFileList().removeAll(); for (File file : all) { listModelFiles.addElement(file); } // ..then use a renderer getFileList().setCellRenderer(new FileRenderer(!vertical)); if (!vertical) { getFileList().setLayoutOrientation(javax.swing.JList.HORIZONTAL_WRAP); getFileList().setVisibleRowCount(-1); } else { getFileList().setVisibleRowCount(9); } return new JScrollPane(getFileList()); } } public void updateActorsFolder() { for(int movieNumberInList = 0; movieNumberInList < getCurrentlySelectedMovieFileList().size(); movieNumberInList++) { if(getCurrentlySelectedMovieFileList().get(movieNumberInList).isDirectory()) { currentlySelectedActorsFolderList.add(new File(getCurrentlySelectedMovieFileList().get(movieNumberInList).getPath() + File.separator + ".actors")); } else if(getCurrentlySelectedMovieFileList().get(movieNumberInList).isFile()) { currentlySelectedActorsFolderList.add(new File(getCurrentlySelectedDirectoryList().getPath() + File.separator + ".actors")); } } } public void setMainGUIEnabled(boolean value) { if(value) frmMovieScraperBlocker.unBlock(); else if(!value) frmMovieScraperBlocker.block(); } public File[] actorFolderFiles(int movieNumberInList) { ArrayList<File> actorFiles = new ArrayList<>(); if(movieToWriteToDiskList != null && movieToWriteToDiskList.size() > 0 && movieToWriteToDiskList.size() > movieNumberInList && movieToWriteToDiskList.get(movieNumberInList) != null && movieToWriteToDiskList.get(movieNumberInList).getActors() != null) { if(currentlySelectedActorsFolderList != null && currentlySelectedActorsFolderList.get(movieNumberInList).isDirectory()) { for (Actor currentActor : movieToWriteToDiskList.get(movieNumberInList).getActors()) { String currentActorNameAsPotentialFileName = currentActor.getName().replace(' ', '_'); File [] listFiles = currentlySelectedActorsFolderList.get(movieNumberInList).listFiles(); for(File currentFile : listFiles) { if(currentFile.isFile() && FilenameUtils.removeExtension(currentFile.getName()).equals(currentActorNameAsPotentialFileName)){ actorFiles.add(currentFile); } } } } } return actorFiles.toArray(new File[actorFiles.size()]); } public FileDetailPanel getFileDetailPanel() { return fileDetailPanel; } public List<File> getCurrentFile() { if (getCurrentlySelectedMovieFileList().size() > 0) return getCurrentlySelectedMovieFileList(); return null; } public JFrame getFrmMoviescraper() { return frmMoviescraper; } public List<File> getCurrentlySelectedActorsFolderList() { return currentlySelectedActorsFolderList; } public void setCurrentlySelectedActorsFolderList( List<File> currentlySelectedActorsFolderList) { this.currentlySelectedActorsFolderList = currentlySelectedActorsFolderList; } public List<Movie> getMovieToWriteToDiskList() { return movieToWriteToDiskList; } public void setMovieToWriteToDiskList(List<Movie> movieToWriteToDiskList) { this.movieToWriteToDiskList = movieToWriteToDiskList; } public List<File> getCurrentlySelectedMovieFileList() { return currentlySelectedMovieFileList; } public void setCurrentlySelectedMovieFileList( List<File> currentlySelectedMovieFileList) { this.currentlySelectedMovieFileList = currentlySelectedMovieFileList; } public List<File> getCurrentlySelectedPosterFileList() { return currentlySelectedPosterFileList; } public void setCurrentlySelectedPosterFileList( List<File> currentlySelectedPosterFileList) { this.currentlySelectedPosterFileList = currentlySelectedPosterFileList; } public List<File> getCurrentlySelectedFanartFileList() { return currentlySelectedFanartFileList; } public void setCurrentlySelectedFanartFileList( List<File> currentlySelectedFanartFileList) { this.currentlySelectedFanartFileList = currentlySelectedFanartFileList; } public DirectoryChooser getChooser() { return chooser; } public void setChooser(DirectoryChooser chooser) { this.chooser = chooser; } public MoviescraperPreferences getPreferences() { return preferences; } public GuiSettings getGuiSettings() { return guiSettings; } public File getCurrentlySelectedDirectoryList() { return currentlySelectedDirectoryList; } public void setCurrentlySelectedDirectoryList( File currentlySelectedDirectoryList) { this.currentlySelectedDirectoryList = currentlySelectedDirectoryList; } public List<File> getCurrentlySelectedNfoFileList() { return currentlySelectedNfoFileList; } public void setCurrentlySelectedNfoFileList( List<File> currentlySelectedNfoFileList) { this.currentlySelectedNfoFileList = currentlySelectedNfoFileList; } public List<File> getCurrentlySelectedFolderJpgFileList() { return currentlySelectedFolderJpgFileList; } public void setCurrentlySelectedFolderJpgFileList( List<File> currentlySelectedFolderJpgFileList) { this.currentlySelectedFolderJpgFileList = currentlySelectedFolderJpgFileList; } public List<File> getCurrentlySelectedTrailerFileList() { return currentlySelectedTrailerFileList; } public void setCurrentlySelectedTrailerFileList( List<File> currentlySelectedTrailerFileList) { this.currentlySelectedTrailerFileList = currentlySelectedTrailerFileList; } public JList<File> getFileList() { return fileList; } public void setFileList(JList<File> fileList) { this.fileList = fileList; } public ProgressMonitor getProgressMonitor() { if (progressMonitor == null) progressMonitor = new ProgressMonitor(frmMoviescraper); return progressMonitor; } public void setProgressMonitor(ProgressMonitor progressMonitor) { this.progressMonitor = progressMonitor; } public void showMessageConsolePanel(){ messageConsolePanel.setVisible(true); guiSettings.setShowOutputPanel(true); } public void hideMessageConsolePanel(){ messageConsolePanel.setVisible(false); guiSettings.setShowOutputPanel(false); } public void showButtonPanel(){ buttonPanel.setVisible(true); guiSettings.setShowToolbar(true); } public void hideButtonPanel(){ buttonPanel.setVisible(false); guiSettings.setShowToolbar(false); } public String getOriginalJavLibraryMovieTitleBeforeAmalgamate() { return originalJavLibraryMovieTitleBeforeAmalgamate; } public void setOriginalJavLibraryMovieTitleBeforeAmalgamate( String originalJavLibraryMovieTitleBeforeAmalgamate) { this.originalJavLibraryMovieTitleBeforeAmalgamate = originalJavLibraryMovieTitleBeforeAmalgamate; } public boolean showAmalgamationSettingsDialog() { AmalgamationSettingsDialog dialog = new AmalgamationSettingsDialog(this, getAllAmalgamationOrderingPreferences()); return dialog.show(); } public AllAmalgamationOrderingPreferences getAllAmalgamationOrderingPreferences() { //rereading from file in case external program somehow decides to change this file before we get it. //also this fixes a bug where canceling a scrape somehow corrupted the variable and caused an error when opening the //amalgamation settings dialog allAmalgamationOrderingPreferences = allAmalgamationOrderingPreferences.initializeValuesFromPreferenceFile(); return allAmalgamationOrderingPreferences; } public void setAllAmalgamationOrderingPreferences( AllAmalgamationOrderingPreferences allAmalgamationOrderingPreferences) { this.allAmalgamationOrderingPreferences = allAmalgamationOrderingPreferences; } public void enableFileWrite() { menuBar.enableWriteFile(); buttonPanel.enableWriteFile(); } public void disableFileWrite() { menuBar.disableWriteFile(); buttonPanel.disableWriteFile(); } }