/home/wpollock1/public_html/restricted/Java2/SearchEngine/com/wpollock/searchengine/Main.java

/* Search Engine - Java files and collections project (model solution).
 * In this solution, I used a custom (UTF-8 text, with long lines) file format,
 * and the Prefs API to store the window position and name of the data file.
 * (The GUI is simple, and will go away someday when this application is
 * converted to a web application.)  The file format is documented in the
 * file "FileFormat.txt", included in the source.  It too may change, maybe
 * to JSON.
 *
 * The use cases for this application are simple: AND, OR, and PHRASE search
 * from the main window, and "Add file" and "remove file" from the
 * "maintenance" window.  (Also a "reset prefs" button, and a "rebuild index"
 * button may be added, time permitting.)
 *
 * Note many methods are static.  Since there is one index file, only one
 * instance of this application should be running at any one time.
 *
 * Written 2/2012 by Wayne Pollock, Tampa Florida USA
 */

package com.wpollock.searchengine;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.prefs.*;
import javax.swing.*;
import javax.swing.event.*;
import static com.wpollock.searchengine.IndexUtils.*;

/**
 * @author Wayne Pollock <pollock@acm.org>
 * @version 1.1
 *
 */
enum SearchType { AND, OR, PHRASE }

public class Main extends JFrame implements ComponentListener {

	private static final long serialVersionUID = 1L;
	static final String DEFAULT_FILE_NAME = "SrchNgn.txt";  // In user's home.
	static final String VERSION = "1.1";  // App's version number

	static String indexFileName = null;  // Pathname to data file.
	static Preferences prefs = null;
	static long nextID;  // Next fileID to use when indexing new files
	static ImageIcon appIcon = null;


	final static JLabel numItemsIndexed = new JLabel( "0" );
	private final JButton searchBtn, aboutBtn;
	private SearchType selectedSearch = SearchType.AND;
	private JTextField searchTerms;
	private JTextPane results;

	static JFrame maintenanceWindow = null;

    // Preference keys for this package
    static final String INDEX_FILE_NAME = "index_file";
    static final String MAIN_X = "Xpos";
    static final String MAIN_Y = "Ypos";
    static final String MAIN_HEIGHT = "height";
    static final String MAIN_WIDTH = "width";
    static final String MAINT_X = "maintXpos";
    static final String MAINT_Y = "maintYpos";
    static final String MAINT_HEIGHT = "maintHeight";
    static final String MAINT_WIDTH = "maintWidth";
	static final String FILE_HEADER = "SearchData 1.0";
	static final String FILE_ENCODING = "UTF-8";
	static final String LAST_DIRECTORY = "LastvisitedDir";
	// static final String JTABLE_DIVIDER_POS = "";  // Use JTable default

	public static void main(String[] args) {
		// Create main window:
		Main mainWindow = new Main();

		indexFileName = prefs.get( INDEX_FILE_NAME, DEFAULT_FILE_NAME );
		String userHomeDir = System.getProperty( "user.home" );
		String sepChar = System.getProperty( "file.separator" );
		indexFileName = userHomeDir + sepChar + indexFileName;

		// Locate and read index data file; if missing, create a new one.
		// If it is found but is unreadable, try to rename it and then
		// create a new one:

		mainWindow.setVisible( true );

		IndexUtils.initializeIndexFromFile(); // read file or create if missing
		maintenanceWindow = new MaintenanceWindow( mainWindow, prefs );
		Main.numItemsIndexed.setText( "" +
				FileListModel.getModel().getRowCount() );

		// If config file empty, start with "Maintenance" window open:
		if ( nextID == 0 )
			maintenanceWindow.setVisible( true );
	}

	// Build the main window GUI:
	public Main () {
		setTitle( "Search Engine" );
		setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

		// Icon found with Creative Commons License:
		appIcon = new ImageIcon(getClass().getResource("MagnifyingGlass.jpg"));
		setIconImage( appIcon.getImage() );

		// Set window bounds from saved prefs:
		prefs = Preferences.userNodeForPackage(Main.class);
		positionWindow();

		// Add components and set their properties:
		Box mainBox = Box.createVerticalBox();
		add ( mainBox );

		// Add application title:
		JLabel title = new JLabel("Search Engine");
		title.setFont( new Font("SansSerif", Font.BOLD, 24) );
		Box titleBox = Box.createHorizontalBox();
		titleBox.add( title );
		titleBox.add( Box.createHorizontalStrut(50) );
		mainBox.add( titleBox );
		mainBox.add( Box.createGlue() );

		// In the middle put a two rows of controls:
		Box searchBox = Box.createHorizontalBox();
		mainBox.add( searchBox );
		mainBox.add( Box.createGlue() );

		searchBox.add( Box.createGlue() );
		searchBox.add( new JLabel("Search Terms:", SwingConstants.RIGHT) );
		searchTerms = new JTextField(40);
		JPanel searchTermsPanel = new JPanel();
		searchTermsPanel.add( searchTerms );
		searchBox.add( searchTermsPanel );

		searchBtn = new JButton( "Search" );
		searchBtn.setMnemonic(KeyEvent.VK_S);
		searchBtn.setToolTipText( "Search indexed files for search terms" );
		searchBtn.setEnabled( false );  // Enable when searchTerms not empty
		getRootPane().setDefaultButton( searchBtn );
		searchBox.add( searchBtn );
		searchBox.add( Box.createGlue() );

		// Create the controls, 3 radiobuttons, a textbox, and a Search button:
		Box btnsBox = Box.createHorizontalBox();
		ButtonGroup group = new ButtonGroup();  // Groups radio buttons

			JRadioButton andBtn = new JRadioButton( "All of the Search Terms" );
			andBtn.setMnemonic(KeyEvent.VK_A);
			andBtn.setActionCommand( "AND" );
			andBtn.setSelected( true );
			group.add( andBtn );
			btnsBox.add( andBtn );

			JRadioButton orBtn = new JRadioButton( "Any of the Search Terms" );
			orBtn.setMnemonic(KeyEvent.VK_O);
			orBtn.setActionCommand( "OR" );
			group.add( orBtn );
			btnsBox.add( orBtn );

			JRadioButton phraseBtn = new JRadioButton( "Exact Phrase" );
			phraseBtn.setMnemonic(KeyEvent.VK_P);
			phraseBtn.setActionCommand( "PHRASE" );
			group.add( phraseBtn );
			btnsBox.add( phraseBtn );
		mainBox.add( btnsBox );
		mainBox.add( Box.createGlue() );

		results = new JTextPane();
		//results.setVisible( false );  // Show after hitting Search button
		results.setEditable( false );
		mainBox.add( results );
		mainBox.add( Box.createGlue() );

		// At  bottom,  show Maint btn, num of files indexed, and app version:
		Box footerBox = Box.createHorizontalBox();
			footerBox.add( Box.createHorizontalStrut(10) );
			JButton maintBtn = new JButton( "Maintenance..." );
			maintBtn.setMnemonic(KeyEvent.VK_M);
			maintBtn.setToolTipText( "Show index maintenance window" );
			footerBox.add( maintBtn );
			footerBox.add( Box.createGlue() );
			footerBox.add( new JLabel("Number of files indexed: ") );
			footerBox.add( numItemsIndexed );
			footerBox.add( Box.createGlue() );

			aboutBtn = new JButton( "About..." );
			aboutBtn.setActionCommand( "ABOUT" );
			aboutBtn.setMnemonic(KeyEvent.VK_B);
			aboutBtn.setToolTipText( "About Search Engine" );
			footerBox.add( aboutBtn );
			footerBox.add( Box.createHorizontalStrut(10) );
		mainBox.add( footerBox );

		// Hook up event handlers:
		addComponentListener( this );  // Handles window resize and move

		searchTerms.requestFocusInWindow();
		searchTerms.requestFocus();
		// If non-empty, enable the Search button:
		searchTerms.getDocument().addDocumentListener(new DocumentListener() {
			@Override
			public void insertUpdate(DocumentEvent e) { updateSearchBtn(); }
			@Override
			public void removeUpdate(DocumentEvent e) { updateSearchBtn(); }
			@Override
			public void changedUpdate(DocumentEvent e) { updateSearchBtn(); }
		});

		maintBtn.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed ( ActionEvent evnt ) {
				searchTerms.requestFocus();
				maintenanceWindow.setVisible( true );
				maintenanceWindow.toFront();
			}
		});

		andBtn.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed ( ActionEvent evnt ) {
				selectedSearch = SearchType.AND;
				searchTerms.requestFocus();
			}
		});

		orBtn.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed ( ActionEvent evnt ) {
				selectedSearch = SearchType.OR;
				searchTerms.requestFocus();
			}
		});

		phraseBtn.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed ( ActionEvent evnt ) {
				selectedSearch = SearchType.PHRASE;
				searchTerms.requestFocus();
			}
		});

		searchBtn.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed ( ActionEvent evnt ) {
				switch ( selectedSearch  ) {
				case AND: doAndSearch(searchTerms.getText(), results);
							break;
				case OR:  doOrSearch(searchTerms.getText(), results);
							break;
				case PHRASE: doPhraseSearch(searchTerms.getText(), results);
							break;
				}
				searchTerms.requestFocus();
			}
		});

		// TODO:  Add a button, to clear old results (and the search textfield)

		aboutBtn.addActionListener( new ActionListener() {
			@Override
			public void actionPerformed ( ActionEvent evnt ) {
				File indexFile = new File( indexFileName );
				long size = indexFile.length();

				JOptionPane.showMessageDialog(null, "Search Engine "
						+ VERSION + "\n"
						+ "Model Solution to COP-2805 Search Engine Project\n"
						+ "Written by Wayne Pollock, Tampa Florida USA\n"
						+ "(Index data file: " + indexFileName + ",\n"
						+ "size: " + size + " bytes)",
						"Search Engine", JOptionPane.INFORMATION_MESSAGE,
						appIcon );
				searchTerms.requestFocus();
			}
		});
}

	private void updateSearchBtn () {
		String terms = searchTerms.getText();
		searchBtn.setEnabled(terms.length() != 0);  // Enable button if usable
	}

	// ComponentListener methods:
	public void componentMoved   (ComponentEvent e) { updatePrefs(); }
	public void componentResized (ComponentEvent e) { updatePrefs(); }
	public void componentShown   (ComponentEvent e) {}
	public void componentHidden  (ComponentEvent e) {}

	/** Store current window bounds into the prefs when the window
	 *  size or position is changed:
	 */
	void updatePrefs () {
		prefs.put(LAST_DIRECTORY, System.getProperty("user.home") );
		prefs.putInt( MAIN_WIDTH, (int) getWidth() );
		prefs.putInt( MAIN_HEIGHT, (int) getHeight() );
		prefs.putInt( MAIN_X, (int) getX() );
		prefs.putInt( MAIN_Y, (int) getY() );
		if ( maintenanceWindow != null && maintenanceWindow.isVisible() ) {
			prefs.putInt( MAINT_WIDTH, (int) maintenanceWindow.getWidth() );
			prefs.putInt( MAINT_HEIGHT, (int) maintenanceWindow.getHeight() );
			prefs.putInt( MAINT_X, (int) maintenanceWindow.getX() );
			prefs.putInt( MAINT_Y, (int) maintenanceWindow.getY() );
		}
	}

	/** Position main window to defaults:
	 */
	void positionWindow () {
		// POsition main window:
		int xPos = prefs.getInt( MAIN_X, -1 );  // "-1" means never set
		int yPos = prefs.getInt( MAIN_Y, -1 );
		int height = prefs.getInt( MAIN_HEIGHT, 600 );
		int width = prefs.getInt( MAIN_WIDTH, 800 );
		setSize( width, height );

	    // if xPos is -1, the window position was never set before,
		// so center it:
		if ( xPos == -1 ) {
			setLocationRelativeTo( null );  // Center on Screen
			prefs.putInt( MAIN_X, getX() );
			prefs.putInt( MAIN_Y, getY() );
		} else
			setLocation( xPos, yPos );
	}
}