Commits

Christopher De Vries  committed 1bcbe08

Initial revision

  • Participants

Comments (0)

Files changed (17)

+<project name="infopad" default="compile" basedir=".">
+
+<target name="init">
+  <property name="depbase" value="${basedir}/deploy"/>
+  <property name="src" value="${basedir}/src"/>
+  <property name="icons" value="${basedir}/icons"/>
+  <property name="helpdoc" value="${basedir}/helpdoc"/>
+  <property name="build" value="${basedir}/build"/>
+  <property name="docdir" value="${build}/doc"/>
+  <property name="fset" value="com/idolstarastronomer/infopad/**"/>
+  <property name="jnlpdir" value="${basedir}/jnlp"/>
+  <property name="jnlpdeploy" value="${depbase}/jnlp"/>
+  <property name="jarname" value="infopad.jar"/>
+  <property name="conf" value="${basedir}/conf"/>
+</target>
+
+<target name="clean" depends="init">
+  <delete dir="${build}"/>
+  <delete dir="${depbase}"/>
+</target>
+
+<target name="prepare" depends="init">
+  <mkdir dir="${build}"/>
+  <mkdir dir="${depbase}"/>
+</target>
+
+<target name="compile" depends="prepare">
+  <javac srcdir="${src}" destdir="${build}" source="1.4" target="1.4"
+  optimize="yes"/>
+  <copy todir="${build}/icons">
+    <fileset dir="${icons}"/>
+  </copy>
+  <copy todir="${build}/helpdoc">
+    <fileset dir="${helpdoc}"/>
+  </copy>
+</target>
+
+<target name="debug" depends="prepare">
+  <javac srcdir="${src}" destdir="${build}" source="1.4" target="1.4"
+  debug="yes"/>
+  <copy todir="${build}/icons">
+    <fileset dir="${icons}"/>
+  </copy>
+  <copy todir="${build}/helpdoc">
+    <fileset dir="${helpdoc}"/>
+  </copy>
+</target>
+
+<target name="jar" depends="compile">
+  <jar jarfile="${depbase}/${jarname}" basedir="${build}"
+  manifest="${conf}/manifest"/>
+</target>
+
+<target name="jarsign" depends="jar">
+  <input message="Type in your key password:" addproperty="keypass"/>
+  <signjar jar="${depbase}/${jarname}" alias="devries" storepass="${keypass}"/>
+</target>
+
+<target name="jnlp" depends="jarsign">
+  <copy todir="${jnlpdeploy}">
+    <fileset dir="${jnlpdir}"/>
+  </copy>
+  <copy file="${depbase}/${jarname}" todir="${jnlpdeploy}"/>
+</target>
+
+</project>

File helpdoc/index.html

+<html>
+<h1 align="center">InfoPad alpha-4</h1>
+<h2 align="center">Christopher De Vries</h2>
+<p>It seems like only yesterday I thought to myself, "If only I had a
+searchable notepad to hold small random stuff on my computer I would be
+completely content in life." This is that thought's story.</p>
+<p>This should be fairly simple to use. InfoPad is modeled on a spirally bound
+note pad, though unlike a spiral note pad it can grow, shrink, and contains a
+search function. Basically just type in anything you
+want into a blank note. You can navigate backward and forwards in your InfoPad
+using the "<code>Previous</code>" and "<code>Next</code>" buttons. You can
+create new blank notes by using the
+"<code>File&gt;New Note</code>" menu option. You can delete notes by using the
+"<code>File&gt;Delete Note</code>" menu option, and of course save notes using
+the "<code>File&gt;Save Notes</code>" menu option. The InfoPad checks every
+minute to see if you have made any changes and automatically saves your notes
+if that is the case.</p>
+<p>Organization of your random notes is achieved through the search bar. As you
+type in the elements of a search matching elements will be highlighted in
+yellow in the
+next note which contains a match. To flip through multiple matching notes click
+the "<code>Find Next</code>" button. If unable to find a match, nothing will be
+highlighted. The search bar can perform regular expression searches making it
+quite powerful.</p>
+
+<h2>Release Notes</h2>
+<p>This is alpha quality software. It seems to work pretty well for me, but
+there are probably problems I have not found yet. Please email me at
+<code>nissyen@users.sourceforge.net</code> if you have any problems or want to
+offer some suggestions. Future versions may be incompatible with the current
+note format, so keep in mind that all your notes may be lost if you upgrade to
+the beta and release versions. After beta versions I will maintain backward
+compatibility in the note files, but I make no guarantees about this
+version. If you need your notes you can always copy them to a text file and
+then copy them back after upgrading.</p>
+
+<h2>Features Planned for Version 1.0</h2>
+<ul>
+<li>Some sort of warning if it's not automatically saving
+<li>The ability to sync up to a note server over the internet.
+<li>Implementing good suggestions from users.
+</ul>
+</html> 

File src/com/idolstarastronomer/infopad/AboutDialog.java

+package com.idolstarastronomer.infopad;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import java.net.URL;
+import java.io.IOException;
+
+public class AboutDialog {
+    JOptionPane dialogPane;
+    MainUI owner;
+
+    public AboutDialog(MainUI owner) {
+	this.owner = owner;
+	dialogPane = new JOptionPane("<html><p align=\"center\"><font size=\"+1\">InfoPad alpha-4</font><br>&copy; 2005<br>Christopher De Vries</html></p>",JOptionPane.INFORMATION_MESSAGE);
+	dialogPane.setIcon(owner.infoPadIcon);
+    }
+
+    public void displayAboutDialog() {
+	JDialog dialog = dialogPane.createDialog(owner,"About InfoPad");
+	dialog.setVisible(true);
+    }
+}

File src/com/idolstarastronomer/infopad/CtrlKListener.java

+package com.idolstarastronomer.infopad;
+import javax.swing.JTextArea;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.InputEvent;
+import javax.swing.text.Document;
+import javax.swing.text.BadLocationException;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+
+public class CtrlKListener extends KeyAdapter {
+    JTextArea textArea;
+    Clipboard clipboard;
+
+    public CtrlKListener(JTextArea textArea) {
+	super();
+	this.textArea = textArea;
+	Toolkit tk = Toolkit.getDefaultToolkit();
+	clipboard = tk.getSystemClipboard();
+    }
+
+    public void keyPressed(KeyEvent e) {
+	if(e.getModifiersEx()==InputEvent.CTRL_DOWN_MASK) {
+	    // ctrl-k (kill to end of line)
+	    if(e.getKeyCode()==KeyEvent.VK_K) {
+		try {
+		    int currpos = textArea.getCaretPosition();
+		    int endpos = textArea.getLineEndOffset(textArea.getLineOfOffset(currpos));
+		    Document doc = textArea.getDocument();
+		    StringSelection killedString = new StringSelection(doc.getText(currpos,endpos-currpos-1));
+		    if(endpos-currpos>1) {
+			doc.remove(currpos,endpos-currpos-1);
+			// textArea.setCaretPosition(currpos);
+			clipboard.setContents(killedString,killedString);
+
+		    }
+		    else {
+			doc.remove(currpos,endpos-currpos);
+			// right now we wont put plant likes in the clipboard
+		    }
+		}
+		catch(BadLocationException ex) {
+		    // This should not happen
+		}
+	    }
+	    else if(e.getKeyCode()==KeyEvent.VK_A) {
+		try {
+		    int currpos = textArea.getCaretPosition();
+		    int startpos = textArea.getLineStartOffset(textArea.getLineOfOffset(currpos));
+
+		    textArea.setCaretPosition(startpos);
+		}
+		catch(BadLocationException ex) {
+		    // This also should not happen
+		}
+	    }
+	    else if(e.getKeyCode()==KeyEvent.VK_E) {
+		try {
+		    int currpos = textArea.getCaretPosition();
+		    int endpos = textArea.getLineEndOffset(textArea.getLineOfOffset(currpos));
+		    
+		    textArea.setCaretPosition(endpos-1);
+		}
+		catch(BadLocationException ex) {
+		    // And the same with this
+		}
+	    }
+	}
+    }
+}
+

File src/com/idolstarastronomer/infopad/DocumentChangeListener.java

+package com.idolstarastronomer.infopad;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Document;
+import javax.swing.text.BadLocationException;
+
+public class DocumentChangeListener implements DocumentListener {
+    MainApp app;
+
+    public DocumentChangeListener() {
+	app = MainApp.getInstance();
+    }
+
+    public void anyChange(DocumentEvent e) {
+	Document d;
+	String noteContent;
+	
+	// After a change we clear the highlights
+	app.ui.clearAllHighlights();
+
+	d = e.getDocument();
+	try {
+	    noteContent = d.getText(0,d.getLength());
+	    app.noteList.getCurrent().setContents(noteContent);
+
+	}
+	catch(BadLocationException ex) {
+	    // Shouldn't get here
+	}
+	app.periodicSave.scheduleSave();
+    }
+
+    public void changedUpdate(DocumentEvent e) {
+	anyChange(e);
+    }
+
+    public void insertUpdate(DocumentEvent e) {
+	anyChange(e);
+    }
+
+    public void removeUpdate(DocumentEvent e) {
+	anyChange(e);
+    }
+}

File src/com/idolstarastronomer/infopad/HelpWindow.java

+package com.idolstarastronomer.infopad;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import java.net.URL;
+import java.io.IOException;
+
+public class HelpWindow extends JDialog {
+    
+    public HelpWindow(Frame owner) {
+	super(owner,"InfoPad Help");
+
+	JEditorPane htmlPane;
+	JScrollPane htmlScroll;
+	ClassLoader cl = getClass().getClassLoader();
+
+	htmlPane = new JEditorPane();
+	htmlPane.setEditable(false);
+	htmlPane.setBorder(BorderFactory.createEmptyBorder(15,15,15,15));
+	htmlPane.setContentType("text/html");
+	URL helpDocUrl = cl.getResource("helpdoc/index.html");
+
+	try {
+	    htmlPane.setPage(helpDocUrl);
+	}
+	catch(IOException e) {
+	    htmlPane.setText("<pre>Unable to load help documentation</pre>");
+	}
+
+	htmlScroll = new JScrollPane(htmlPane);
+
+	htmlScroll.setPreferredSize(new Dimension(400,300));
+
+	getContentPane().add(htmlScroll);
+	// setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+	pack();
+    }
+
+    public void displayHelp() {
+	setVisible(true);
+    }
+}

File src/com/idolstarastronomer/infopad/MacOSSpecificCode.java

+package com.idolstarastronomer.infopad;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+
+public class MacOSSpecificCode {
+    static boolean isMacOSX;
+    static boolean isMacOSXSet=false;
+
+    public static boolean isMacOSX() {
+	if(!isMacOSXSet) {
+	    String lcOSName = System.getProperty("os.name").toLowerCase();
+	    isMacOSX = lcOSName.startsWith("mac os x");
+	    isMacOSXSet=true;
+	}
+	return isMacOSX;
+    }
+
+    public static void createApplicationMenuListeners() {
+	// Set up Mac Application Menu's exit and about to work
+	// Must use reflection and a proxy to make this compile everywhere
+	// Based on Apple Mailing list post from Eric Eslinger
+	try {
+	    Class applicationClass = Class.forName("com.apple.eawt.Application");
+	    final Class applicationListenerClass = Class.forName("com.apple.eawt.ApplicationListener");
+	    Object applicationObject = applicationClass.newInstance();
+	    ClassLoader appClassLoader = applicationClass.getClassLoader();
+	    Class[] interfaces = {applicationListenerClass};
+
+	    Object applicationListenerProxy = Proxy.newProxyInstance(appClassLoader,interfaces,new InvocationHandler() {
+		    public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
+			if(m.getDeclaringClass().equals(applicationListenerClass)) {
+			    if(m.getName().equals("handleQuit")) {
+				MainApp app = MainApp.getInstance();
+
+				app.terminateProgram();
+				return null;
+			    }
+			    else if(m.getName().equals("handleAbout")) {
+				Object appEventObject = args[0];
+				Class[] appEventargs = {Boolean.TYPE};
+				Method setHandled = appEventObject.getClass().getMethod("setHandled",appEventargs);
+				Object[] invokeargs = {Boolean.valueOf(true)};
+				setHandled.invoke(appEventObject,invokeargs);
+				MainApp app = MainApp.getInstance();
+				app.ui.aboutDialog.displayAboutDialog();
+
+				return null;
+			    }
+			    else {
+				return null;
+			    }
+			}
+			else {
+			    return m.invoke(proxy,args);
+			}
+		    }
+		});
+
+	    Object[] args = {applicationListenerProxy};
+	    Class[] addAppListenerArgs = {applicationListenerClass};
+	    Method addAppListenerMethod = applicationClass.getMethod("addApplicationListener",addAppListenerArgs);
+	    addAppListenerMethod.invoke(applicationObject,args);
+	}
+	catch(Exception e) {
+	    System.err.println("Application Menu Initialization Error");
+	    e.printStackTrace();
+	    System.exit(1);
+	}
+    }
+
+    public static void setMacSpecificProperties() {
+	System.setProperty("apple.laf.useScreenMenuBar","true");
+	System.setProperty("apple.awt.showGrowBox","false");
+	System.setProperty("apple.awt.brushMetalLook","true");
+    }
+}

File src/com/idolstarastronomer/infopad/MainApp.java

+package com.idolstarastronomer.infopad;
+import javax.swing.SwingUtilities;
+import java.io.*;
+import javax.swing.JOptionPane;
+
+public class MainApp {
+    static MainApp instance = null;
+    public MainUI ui=null;
+    public NoteList noteList;
+    SerializationManager noteListSerializationManager;
+    PeriodicSaveThread periodicSave;
+    File noteFile;
+    boolean continueTermination;
+
+    private MainApp() {
+	noteList = new NoteList();
+	noteFile = new File(System.getProperty("user.home"),".infopad_nots");
+	noteListSerializationManager = new SerializationManager(noteFile,noteList);
+
+	// Mac OS Specifics
+	if(MacOSSpecificCode.isMacOSX()) {
+	    MacOSSpecificCode.setMacSpecificProperties();
+	}
+
+	if(noteFile.exists()) {
+	    try {
+		noteList = (NoteList)noteListSerializationManager.load();
+
+	    }
+	    catch(Exception e) {
+		// Must not exist or be readable, or not right class?
+	    }
+	}
+
+	periodicSave = new PeriodicSaveThread(noteListSerializationManager);
+	periodicSave.start();
+    }
+
+    public void initializeUserInterface() {
+	SwingUtilities.invokeLater(new Runnable() {
+		public void run() {
+		    ui = new MainUI();
+		    ui.displayNote(noteList.getCurrent());
+		}
+	    });
+    }
+
+    public void terminateProgram() {
+	continueTermination=true;
+	try {
+	    noteListSerializationManager.save();
+	}
+	catch(IOException e) {
+	    Object[] options = {"OK", "Cancel"};
+	    int result = JOptionPane.showOptionDialog(this.ui,"Unable to save to\n"+this.noteFile.toString()+"\nFile Permissions may need to be changed.\nExit without saving?","Warning",JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE,null, options, options[1]);
+	    if(result==1 || result==JOptionPane.CLOSED_OPTION) continueTermination=false;
+	}
+	if(continueTermination) System.exit(0);
+    }
+
+    // Wrap the UI display note in the swing event loop
+    public void displayNote(final Note n) {
+	SwingUtilities.invokeLater(new Runnable() {
+		public void run() {
+		    ui.displayNote(n);
+		}
+	    });
+    }
+
+    public static MainApp getInstance() {
+	if(instance==null) {
+	    instance = new MainApp();
+	}
+	return instance;
+    }
+
+    public static void main(String[] args) {
+	MainApp app;
+
+	app = MainApp.getInstance();
+	app.initializeUserInterface();
+
+    }
+}

File src/com/idolstarastronomer/infopad/MainUI.java

+package com.idolstarastronomer.infopad;
+
+import java.awt.*;
+import java.awt.event.*;
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.text.*;
+import java.io.IOException;
+import java.util.regex.Matcher;
+
+public class MainUI extends JFrame {
+    JTextArea notePad;
+    JTextField searchField;
+    YellowHighlightPainter myHighlightPainter;
+    Highlighter notePadHighlighter;
+    HelpWindow helpWindow;
+    AboutDialog aboutDialog;
+    Icon infoPadIcon;
+
+    public MainUI() {
+	super("InfoPad");
+	ClassLoader cl = getClass().getClassLoader();
+	Toolkit tk = Toolkit.getDefaultToolkit();
+	int shortcutKeyMask;
+
+	// Set the accelerator shortcut key mask to default for platform
+	try {
+	    shortcutKeyMask = tk.getMenuShortcutKeyMask();
+	}
+	catch(HeadlessException e) {
+	    // It's going to be hard to use this program without a head
+	    shortcutKeyMask = Event.CTRL_MASK;
+	}
+
+	// Set look and feel to native
+	try {
+	    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+	}
+	catch(Exception e) { }
+
+	// X11 Tweak:
+	// Let the window manager choose the window location.
+	UnixSpecificCode.letWindowManagerPlaceWindow(this);	
+	
+	// Get an instance of the yellow highlight painter
+	// to highlight searches
+	myHighlightPainter = new YellowHighlightPainter();
+
+	// Set a frame icon
+	Image iconImage = tk.getImage(cl.getResource("icons/icon-trans-small.png"));
+	setIconImage(iconImage);
+	infoPadIcon = new ImageIcon(iconImage);
+
+	// Run program termination if the window is closed
+	setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
+	addWindowListener(new WindowAdapter() {
+		public void windowClosed(WindowEvent e) {
+		    MainApp.getInstance().terminateProgram();
+		}
+	    });
+
+	// Stickynote Tab
+	// Stickynote note area 
+	// I create this so that the menus can refer to it
+	notePad = new JTextArea();
+	notePad.setLineWrap(true);
+	notePad.setWrapStyleWord(true);
+	notePad.setBorder(BorderFactory.createEmptyBorder(8,8,8,8));
+	notePad.requestFocusInWindow();
+	notePad.addMouseListener(new MouseInputAdapter() {
+		public void mouseEntered(MouseEvent e) {
+		    notePad.requestFocusInWindow();
+		}
+	    });
+
+	// Add a mouse listener to insert selection into component with middle
+	// mouse button (Unix only?)
+	// notePad.addMouseListener(new UnixSelectionPasteListener(notePad));
+
+	// Set ctrl-a and ctrl-e to navigate to beginning and end of lines
+	// ctrl-k deletes to end of line
+	notePad.addKeyListener(new CtrlKListener(notePad));
+
+	// Get the notePad's highlighter for use with searches
+	notePadHighlighter = notePad.getHighlighter();
+
+	// Make a listener to sync changes with the current note
+	notePad.getDocument().addDocumentListener(new DocumentChangeListener());
+
+	final JScrollPane noteScroll = new JScrollPane(notePad);
+	noteScroll.setPreferredSize(new Dimension(300,200));
+	noteScroll.setBorder(BorderFactory.createEmptyBorder());
+
+	// Menu Bar
+	final JMenuBar menubar = new JMenuBar();
+	setJMenuBar(menubar);
+
+	JMenu filemenu = new JMenu("File");
+	filemenu.setMnemonic(KeyEvent.VK_F);
+	JMenu editmenu = new JMenu("Edit");
+	editmenu.setMnemonic(KeyEvent.VK_E);
+	JMenu helpmenu = new JMenu("Help");
+	helpmenu.setMnemonic(KeyEvent.VK_H);
+	menubar.add(filemenu);
+	menubar.add(editmenu);
+	if(!MacOSSpecificCode.isMacOSX()) {
+	    menubar.add(Box.createHorizontalGlue());
+	}
+	menubar.add(helpmenu);
+
+	// File Menu
+	JMenuItem newNote = new JMenuItem("New Note");
+	newNote.setMnemonic(KeyEvent.VK_N);
+	newNote.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, shortcutKeyMask));
+	JMenuItem deleteNote = new JMenuItem("Delete Note");
+	deleteNote.setMnemonic(KeyEvent.VK_D);
+	deleteNote.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, shortcutKeyMask));
+	JMenuItem saveNotes = new JMenuItem("Save Notes");
+	saveNotes.setMnemonic(KeyEvent.VK_S);
+	saveNotes.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, shortcutKeyMask));
+	filemenu.add(newNote);
+	filemenu.add(deleteNote);
+	filemenu.add(saveNotes);
+
+	// We'll add an Exit key, unless this is OS X in which case
+	// they have a quit key in the application menu.
+	if(!MacOSSpecificCode.isMacOSX()) {
+	    JMenuItem exit = new JMenuItem("Exit");
+	    exit.setMnemonic(KeyEvent.VK_X);
+	    exit.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Q, shortcutKeyMask));
+	    filemenu.addSeparator();
+	    filemenu.add(exit);
+	    
+	    exit.addActionListener(new ActionListener() {
+		    public void actionPerformed(ActionEvent e) {
+			MainApp.getInstance().terminateProgram();
+		    }
+		});
+	}
+
+	newNote.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		    MainApp app = MainApp.getInstance();
+		    app.noteList.insert(new Note());
+		    displayNote(app.noteList.getCurrent());
+		}
+	    });
+
+	deleteNote.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		    MainApp app = MainApp.getInstance();
+		    app.noteList.remove();
+		    displayNote(app.noteList.getCurrent());
+		}
+	    });
+	saveNotes.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		    MainApp app = MainApp.getInstance();
+		    try {
+			app.noteListSerializationManager.save();
+		    }
+		    catch(IOException outputException) {
+			JOptionPane.showMessageDialog(app.ui,"Unable to save to\n"+app.noteFile.toString()+"\nPlease check permissions.","Warning",JOptionPane.WARNING_MESSAGE);
+		    }
+		}
+	    });
+	
+	// Edit Menu
+	Action cutAction = notePad.getActionMap().get(DefaultEditorKit.cutAction);
+	cutAction.putValue(Action.NAME, "Cut");
+	cutAction.putValue(Action.MNEMONIC_KEY,new Integer(KeyEvent.VK_T));
+	cutAction.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_X,shortcutKeyMask));
+	JMenuItem cutItem = new JMenuItem(cutAction);
+
+	Action copyAction = notePad.getActionMap().get(DefaultEditorKit.copyAction);
+	copyAction.putValue(Action.NAME, "Copy");
+	copyAction.putValue(Action.MNEMONIC_KEY,new Integer(KeyEvent.VK_C));
+	copyAction.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_C,shortcutKeyMask));
+	JMenuItem copyItem = new JMenuItem(copyAction);
+
+	Action pasteAction = notePad.getActionMap().get(DefaultEditorKit.pasteAction);
+	pasteAction.putValue(Action.NAME,"Paste");
+	pasteAction.putValue(Action.MNEMONIC_KEY,new Integer(KeyEvent.VK_V));
+	pasteAction.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_V,shortcutKeyMask));
+	JMenuItem pasteItem = new JMenuItem(pasteAction);
+
+	// I want to use ctrl-a to move to the beginning of a line
+	int selectAllMask;
+	if(shortcutKeyMask==Event.CTRL_MASK) {
+	    selectAllMask = Event.ALT_MASK;
+	}
+	else {
+	    selectAllMask = shortcutKeyMask;
+	}
+
+	Action selectAllAction = notePad.getActionMap().get(DefaultEditorKit.selectAllAction);
+	selectAllAction.putValue(Action.NAME, "Select All");
+	selectAllAction.putValue(Action.MNEMONIC_KEY,new Integer(KeyEvent.VK_A));
+	selectAllAction.putValue(Action.ACCELERATOR_KEY,KeyStroke.getKeyStroke(KeyEvent.VK_A,selectAllMask));
+	JMenuItem selectAllItem = new JMenuItem(selectAllAction);
+
+	editmenu.add(cutItem);
+	editmenu.add(copyItem);
+	editmenu.add(pasteItem);
+	editmenu.add(selectAllItem);
+
+	// Help Menu
+	JMenuItem helpItem = new JMenuItem("InfoPad Help");
+	helpItem.setMnemonic(KeyEvent.VK_I);
+	helpmenu.add(helpItem);
+	helpWindow = new HelpWindow(this);
+	helpItem.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		    helpWindow.displayHelp();
+		}
+	    });
+	
+	aboutDialog = new AboutDialog(this);
+	if(!MacOSSpecificCode.isMacOSX()) {
+	    JMenuItem aboutInfoPad = new JMenuItem("About InfoPad");
+	    aboutInfoPad.setMnemonic(KeyEvent.VK_A);
+	    helpmenu.add(aboutInfoPad);
+	    aboutInfoPad.addActionListener(new ActionListener() {
+		    public void actionPerformed(ActionEvent e) {
+			aboutDialog.displayAboutDialog();
+		    }
+		});
+	}
+
+	// Stickynote navigation buttons
+	final JButton prevNoteButton = new JButton("Previous");
+	final JButton nextNoteButton = new JButton("Next");
+	final JPanel noteNavPanel = new JPanel();
+	noteNavPanel.setLayout(new BoxLayout(noteNavPanel, BoxLayout.LINE_AXIS));
+	noteNavPanel.add(prevNoteButton);
+	noteNavPanel.add(Box.createHorizontalGlue());
+	noteNavPanel.add(nextNoteButton);
+	noteNavPanel.setBackground(Color.WHITE);
+	noteNavPanel.setBorder(BorderFactory.createEmptyBorder());
+
+	// Navigation Button Action Listeners
+	prevNoteButton.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		    MainApp app = MainApp.getInstance();
+		    displayNote(app.noteList.getPrevious());
+		}
+	    });
+
+	nextNoteButton.addActionListener(new ActionListener() {
+		public void actionPerformed(ActionEvent e) {
+		    MainApp app = MainApp.getInstance();
+		    displayNote(app.noteList.getNext());
+		}
+	    });
+
+	// Sticklnote create/delete buttons
+	// final JButton newNoteButton = new JButton("New");
+	// final JButton delNoteButton = new JButton("Delete");
+	// final JPanel noteLifePanel = new JPanel();
+	// noteLifePanel.setLayout(new BoxLayout(noteLifePanel, BoxLayout.LINE_AXIS));
+	// noteLifePanel.add(newNoteButton);
+	// noteLifePanel.add(Box.createHorizontalGlue());
+	// noteLifePanel.add(delNoteButton);
+
+	// Stickynote Panel creation
+	final JPanel notePanel = new JPanel();
+	notePanel.setLayout(new BorderLayout());
+	// notePanel.add(noteLifePanel,BorderLayout.NORTH);
+	notePanel.add(noteNavPanel,BorderLayout.SOUTH);
+	notePanel.add(noteScroll,BorderLayout.CENTER);
+	notePanel.setBorder(BorderFactory.createEmptyBorder(3,3,3,3));
+
+	// Tabbed Pane --- For possible future tabs
+	// final JTabbedPane tabbedPane = new JTabbedPane();
+	// tabbedPane.addTab("Notes",notePanel);
+
+	// Search Pane
+	JLabel searchLabel = new JLabel("Search: ");
+	searchField = new JTextField(15);
+	JButton searchButton = new JButton("Find Next");
+	JPanel searchPanel = new JPanel();
+	searchLabel.setLabelFor(searchField);
+	searchPanel.add(searchLabel);
+	searchPanel.add(searchField);
+	searchPanel.add(searchButton);
+
+	searchField.getDocument().addDocumentListener(new SearchChangeListener());
+	searchButton.addActionListener(new SearchButtonListener());
+
+	// Set up Main Window
+	Container mainPanel = getContentPane();
+	// mainPanel.add(tabbedPane,BorderLayour.CENTER);
+	mainPanel.add(notePanel,BorderLayout.CENTER);
+	mainPanel.add(searchPanel,BorderLayout.SOUTH);
+
+	// Set text area to receive focus
+	addWindowListener(new WindowAdapter() {
+		public void windowActivated(WindowEvent e) {
+		    notePad.requestFocusInWindow();
+		}
+	    });
+
+	if(MacOSSpecificCode.isMacOSX()) {
+	    MacOSSpecificCode.createApplicationMenuListeners();
+	}
+
+	pack();
+	setVisible(true);
+    }
+
+    public void highlightAllMatches(Matcher searchMatch) throws BadLocationException {
+	int start = 0;
+
+	notePadHighlighter.removeAllHighlights();
+	while(searchMatch.find(start)) {
+	    // Sometimes emptiness fits, but why highlight it?
+	    if(start!=searchMatch.end()) {
+		notePadHighlighter.addHighlight(searchMatch.start(),searchMatch.end(),myHighlightPainter);
+		if(start==0) {
+		    // Let's move to the end of the first match
+		    notePad.setCaretPosition(searchMatch.end());
+		}
+		start = searchMatch.end();
+	    }
+	    else {
+		// When the match is empty we have to keep moving
+		// otherwise we never exit this loop
+		start++;
+	    }
+	}
+    }
+
+    public void displayNote(Note n) {
+	notePad.setText(n.getContents());
+	notePad.setCaretPosition(0);
+    }
+	
+    public void clearAllHighlights() {
+	notePadHighlighter.removeAllHighlights();
+    }
+
+}

File src/com/idolstarastronomer/infopad/Note.java

+package com.idolstarastronomer.infopad;
+import java.util.Date;
+import java.io.Serializable;
+
+public class Note implements Serializable, Comparable {
+    String contents;
+    Date creationDate;
+    Date modificationDate;
+
+    public Note(String contents) {
+	creationDate = new Date();
+	modificationDate = creationDate;
+	this.contents=contents;
+    }
+
+    public Note() {
+	this("");
+    }
+
+    public void setContents(String contents) {
+	modificationDate = new Date();
+	this.contents=contents;
+    }
+
+    public String getContents() {
+	return contents;
+    }
+
+    public Date getCreationDate() {
+	return creationDate;
+    }
+
+    public Date getModificationDate() {
+	return modificationDate;
+    }
+
+    public int compareTo(Object o) {
+	Note note = (Note)o;
+	return this.modificationDate.compareTo(note.modificationDate);
+    }
+}

File src/com/idolstarastronomer/infopad/NoteList.java

+package com.idolstarastronomer.infopad;
+import java.util.LinkedList;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+import java.io.Serializable;
+import java.util.Date;
+
+public class NoteList implements Serializable {
+    int currentNoteIndex;
+    LinkedList notes;
+    transient int lastSearchPage;
+    static final long serialVersionUID = -9058829022067804539L;
+
+    public NoteList() {
+	notes = new LinkedList();
+	append(new Note("Welcome to InfoPad\n\nTo get started delete this note and start making notes of your own. You can use the search bar to search through your notes and navigate using the previous and next buttons. Look under the file menu to create new notes, delete notes, and save notes to disk."));
+	currentNoteIndex=0;
+	lastSearchPage=0;
+    }
+
+    public synchronized Note getCurrent() {
+	return (Note)notes.get(currentNoteIndex);
+    }
+
+    public synchronized Note getNext() {
+	currentNoteIndex++;
+	if(currentNoteIndex==notes.size()) {
+	    currentNoteIndex=0;
+	}
+
+	return (Note)notes.get(currentNoteIndex);
+    }
+
+    public synchronized Note getPrevious() {
+	if(currentNoteIndex==0) {
+	    currentNoteIndex=notes.size();
+	}
+	currentNoteIndex--;
+
+	return (Note)notes.get(currentNoteIndex);
+    }
+
+    public synchronized void insert(Note n) {
+	notes.add(currentNoteIndex,n);
+    }
+
+    public synchronized void append(Note n) {
+	notes.add(n);
+    }
+
+    public synchronized void remove() {
+	notes.remove(currentNoteIndex);
+	if(currentNoteIndex==notes.size()) {
+	    currentNoteIndex=0;
+	}
+	if(notes.size()==0) {
+	    notes.add(new Note());
+	}
+    }
+	
+    public synchronized Matcher beginSearch(Pattern p) {
+	Note n = getCurrent();
+	Matcher m = p.matcher(n.getContents());
+	lastSearchPage = currentNoteIndex;
+	while(!m.find()) {
+	    n = getNext();
+	    m = p.matcher(n.getContents());
+	    if(lastSearchPage==currentNoteIndex) break;
+	}
+	return m;
+    }
+
+    public synchronized Matcher continueSearch(Pattern p) {
+	Note n = getNext();
+	Matcher m = p.matcher(n.getContents());
+	while(!m.find()) {
+	    n = (Note)getNext();
+	    m = p.matcher(n.getContents());
+	    if(lastSearchPage==currentNoteIndex) break;
+	}
+	return m;
+    }
+
+    public synchronized Note getRecentlyModifiedNote() {
+	Note n;
+	Date resultDate=null;
+	int resultNoteIndex=0;
+	
+	for(currentNoteIndex=0;currentNoteIndex<notes.size();currentNoteIndex++) {
+	    n=(Note)notes.get(currentNoteIndex);
+	    if(resultDate==null) {
+		resultDate = n.getModificationDate();
+		resultNoteIndex = currentNoteIndex;
+	    }
+	    else if(resultDate.compareTo(n.getModificationDate())>0) {
+		resultDate = n.getModificationDate();
+		resultNoteIndex = currentNoteIndex;
+	    }
+	}
+
+	currentNoteIndex=resultNoteIndex;
+	return getCurrent();
+    }
+}
+
+
+

File src/com/idolstarastronomer/infopad/PeriodicSaveThread.java

+package com.idolstarastronomer.infopad;
+import java.io.IOException;
+
+public class PeriodicSaveThread extends Thread {
+    SerializationManager sm;
+    boolean keepRunning = false;
+    boolean doSave = false;
+
+    PeriodicSaveThread(SerializationManager sm) {
+	this.sm=sm;
+	setDaemon(true);
+    }
+
+    public void run() {
+	keepRunning=true;
+	while(keepRunning) {
+	    if(isSaveScheduled()) {
+		unscheduleSave();
+		try {
+		    sm.save();
+		}
+		catch(IOException ioe) {
+		    // We should issue some sort of warning I think
+		}
+	    }
+	    try {
+		sleep(60000);
+	    }
+	    catch(InterruptedException ie) { 
+		// Just continue loop in interrupted
+	    }
+	}
+    }
+
+    public synchronized void scheduleSave() {
+	doSave = true;
+    }
+
+    public synchronized boolean isSaveScheduled() {
+	return doSave;
+    }
+
+    public synchronized void unscheduleSave() {
+	doSave = false;
+    }
+
+    public synchronized void scheduleStop() {
+	keepRunning = false;
+    }
+
+}

File src/com/idolstarastronomer/infopad/SearchButtonListener.java

+package com.idolstarastronomer.infopad;
+import java.awt.event.ActionListener;
+import java.awt.event.ActionEvent;
+import java.util.regex.*;
+
+public class SearchButtonListener implements ActionListener {
+    MainApp app;
+
+    public SearchButtonListener() {
+	app = MainApp.getInstance();
+    }
+
+    public void actionPerformed(ActionEvent e) {
+	String searchContent;
+	Pattern searchPattern;
+	Matcher searchMatch;
+
+	try {
+	    searchContent = app.ui.searchField.getText();
+	    if(searchContent.length()>0) {
+		searchPattern = Pattern.compile(searchContent, Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);
+		searchMatch = app.noteList.continueSearch(searchPattern);
+		app.ui.displayNote(app.noteList.getCurrent());
+		app.ui.highlightAllMatches(searchMatch);
+	    }
+	}
+	catch(Exception ex) {
+	    // Bad search or something
+	}
+    }
+
+}

File src/com/idolstarastronomer/infopad/SearchChangeListener.java

+package com.idolstarastronomer.infopad;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.DocumentEvent;
+import javax.swing.text.Document;
+import java.util.regex.*;
+
+public class SearchChangeListener implements DocumentListener {
+    MainApp app;
+
+    public SearchChangeListener() {
+	app = MainApp.getInstance();
+    }
+
+    public void anyChange(DocumentEvent e) {
+	Document d;
+	String searchContent;
+	Pattern searchPattern;
+	Matcher searchMatch;
+
+	d = e.getDocument();
+	if(d.getLength()>0) {
+	    try {
+		searchContent = d.getText(0,d.getLength());
+		searchPattern = Pattern.compile(searchContent, Pattern.CASE_INSENSITIVE|Pattern.MULTILINE);
+		searchMatch = app.noteList.beginSearch(searchPattern);
+		app.ui.displayNote(app.noteList.getCurrent());
+		app.ui.highlightAllMatches(searchMatch);
+	    }
+	    catch(Exception ex) {
+		// Bad search or something
+	    }
+	}
+	else {
+	    app.ui.clearAllHighlights();
+	}
+    }
+
+    public void changedUpdate(DocumentEvent e) {
+	anyChange(e);
+    }
+
+    public void insertUpdate(DocumentEvent e) {
+	anyChange(e);
+    }
+
+    public void removeUpdate(DocumentEvent e) {
+	anyChange(e);
+    }
+}

File src/com/idolstarastronomer/infopad/SerializationManager.java

+package com.idolstarastronomer.infopad;
+import java.io.*;
+
+public class SerializationManager {
+    File savefile;
+    Serializable serObj;
+
+    SerializationManager(File savefile, Serializable serObj) {
+	this.savefile = savefile;
+	this.serObj = serObj;
+    }
+
+    public synchronized void save() throws IOException {
+	ObjectOutputStream out = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream(savefile)));
+	out.writeObject(serObj);
+	out.flush();
+	out.close();
+    }
+
+    public synchronized Serializable load() throws IOException, ClassNotFoundException {
+	ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(new FileInputStream(savefile)));
+	serObj=(Serializable)in.readObject();
+	in.close();
+	return serObj;
+    }
+
+    public synchronized void setSerializableObject(Serializable serObj) {
+	this.serObj = serObj;
+    }
+
+    public synchronized void setSaveFile(File savefile) {
+	this.savefile = savefile;
+    }
+}

File src/com/idolstarastronomer/infopad/UnixSpecificCode.java

+package com.idolstarastronomer.infopad;
+import java.awt.Window;
+import java.lang.reflect.Method;
+
+public class UnixSpecificCode {
+    static boolean isUnix;
+    static boolean isUnixSet=false;
+
+    public static boolean isUnix() {
+	if(!isUnixSet) {
+	    String lcOsName = System.getProperty("os.name").toLowerCase();
+	    if(lcOsName.startsWith("linux")) {
+		isUnix=true;
+	    }
+	    else if(lcOsName.startsWith("sunos")) {
+		isUnix=true;
+	    }
+	    else {
+		isUnix=false;
+	    }
+	}
+	isUnixSet=true;
+	return isUnix;
+    }
+
+    public static void letWindowManagerPlaceWindow(Window window) {
+	try {
+	    Class[] args = {Boolean.TYPE};
+	    Method setLocationByPlatformMethod = window.getClass().getMethod("setLocationByPlatform",args);
+	    Object[] invokeargs = {Boolean.valueOf(true)};
+	    setLocationByPlatformMethod.invoke(window,invokeargs);
+	}
+	catch(Exception e) {
+	    // Don't worry, if it didn't work the program will still run
+	}
+    }
+
+    // Create a middle button listener that inserts text from the Selection
+    // into the component. 
+}

File src/com/idolstarastronomer/infopad/YellowHighlightPainter.java

+package com.idolstarastronomer.infopad;
+
+import javax.swing.text.*;
+import java.awt.Color;
+
+public class YellowHighlightPainter extends DefaultHighlighter.DefaultHighlightPainter {
+    public YellowHighlightPainter() {
+	super(Color.YELLOW);
+    }
+}