Java tutorial
/** * Kuebiko - NoteFrame.java * Copyright 2013 Dave Huffman (dave dot huffman at me dot com). * Open source under the BSD 3-Clause License. */ package dmh.kuebiko.view; import java.awt.Component; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.FocusAdapter; import java.awt.event.FocusEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.Observable; import java.util.Observer; import javax.swing.AbstractAction; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.ListSelectionModel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.text.DefaultEditorKit; import org.apache.commons.lang.SystemUtils; import org.jdesktop.swingx.autocomplete.AutoCompleteDecorator; import dmh.kuebiko.controller.NoteManager; import dmh.kuebiko.model.Note; import dmh.kuebiko.util.ActionManager; import dmh.kuebiko.util.ActionObserverUtil; import dmh.swing.CustomFocusTraversalPolicy; import dmh.swing.GenericObservable; import dmh.swing.huxley.action.InsertDynamicTextAction; import dmh.swing.huxley.constant.TextAction; import dmh.util.Callback; /** * UI frame for displaying and editing notes. * * @author davehuffman */ public class NoteStackFrame extends JFrame { private static final long serialVersionUID = 1L; private static enum Mode { SEARCH, EDIT } private final NoteManager noteMngr; private Mode mode; private final transient ActionManager actionMngr = new ActionManager(); private final GenericObservable observable = new GenericObservable(); private JSplitPane splitPane; private JScrollPane noteTableScroll; private final JLabel stateImageLabel = new JLabel(); private Component horizontalStrut; private JMenuBar menuBar; private JMenuItem newNoteMenuItem; private JMenu fileMenu; private JMenuItem newStackMenuItem; private JMenuItem openStackMenuItem; private JMenuItem closeMenuItem; private JMenuItem closeAllMenuItem; private JMenuItem saveMenuItem; private JMenuItem saveAllMenuItem; private JMenu editMenu; private JMenuItem undoMenuItem; private JMenuItem redoMenuItem; private JMenuItem cutMenuItem; private JMenuItem copyMenuItem; private JMenuItem pasteMenuItem; private JMenuItem deleteNoteMenuItem; private JMenuItem renameNoteMenuItem; private JMenu textMenu; private JMenuItem insertDateMenuItem; JTextField searchText; NoteTable noteTable; NotePanel notePanel; /** * Create the frame. */ public NoteStackFrame(NoteManager noteMngr) { this.noteMngr = noteMngr; // Setup the various actions for the frame. noteMngr.addObserver(new Observer() { @Override public void update(Observable o, Object arg) { boolean stackChanged = (Boolean) arg; toggleUnsavedChangeIndicator(stackChanged); if (!stackChanged) { notePanel.getHuxleyUiManager().resetTextChanged(); } } }); ActionObserverUtil.registerEnMass(actionMngr, observable, new NewNoteAction(this), new OpenNoteAction(this), new DeleteNoteAction(this), new RenameNoteAction(this), new SaveStackAction(this)); // Build the menus. menuBar = new JMenuBar(); setJMenuBar(menuBar); fileMenu = new JMenu("File"); menuBar.add(fileMenu); newNoteMenuItem = new JMenuItem(actionMngr.getAction(NewNoteAction.class)); fileMenu.add(newNoteMenuItem); JMenuItem openNoteMenuItem = new JMenuItem(actionMngr.getAction(OpenNoteAction.class)); fileMenu.add(openNoteMenuItem); deleteNoteMenuItem = new JMenuItem(actionMngr.getAction(DeleteNoteAction.class)); fileMenu.add(deleteNoteMenuItem); renameNoteMenuItem = new JMenuItem(actionMngr.getAction(RenameNoteAction.class)); fileMenu.add(renameNoteMenuItem); fileMenu.addSeparator(); newStackMenuItem = new JMenuItem(actionMngr.getAction(NewStackAction.class)); fileMenu.add(newStackMenuItem); openStackMenuItem = new JMenuItem(actionMngr.getAction(OpenStackAction.class)); fileMenu.add(openStackMenuItem); fileMenu.addSeparator(); closeMenuItem = new JMenuItem("Close"); closeMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_W, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); closeMenuItem.setEnabled(false); fileMenu.add(closeMenuItem); closeAllMenuItem = new JMenuItem("Close All"); closeAllMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_W, InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); closeAllMenuItem.setEnabled(false); fileMenu.add(closeAllMenuItem); fileMenu.addSeparator(); saveMenuItem = new JMenuItem(actionMngr.getAction(SaveStackAction.class)); fileMenu.add(saveMenuItem); saveAllMenuItem = new JMenuItem("Save All"); saveAllMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); saveAllMenuItem.setEnabled(false); fileMenu.add(saveAllMenuItem); editMenu = new JMenu("Edit"); menuBar.add(editMenu); undoMenuItem = new JMenuItem("Undo"); undoMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_Z, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); editMenu.add(undoMenuItem); redoMenuItem = new JMenuItem("Redo"); // FIXME mac-specific keyboard shortcut redoMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); editMenu.add(redoMenuItem); editMenu.addSeparator(); cutMenuItem = new JMenuItem(actionMngr.getAction(DefaultEditorKit.CutAction.class)); cutMenuItem.setText("Cut"); cutMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_X, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); editMenu.add(cutMenuItem); copyMenuItem = new JMenuItem(actionMngr.getAction(DefaultEditorKit.CopyAction.class)); copyMenuItem.setText("Copy"); copyMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); editMenu.add(copyMenuItem); pasteMenuItem = new JMenuItem(actionMngr.getAction(DefaultEditorKit.PasteAction.class)); pasteMenuItem.setText("Paste"); pasteMenuItem.setAccelerator( KeyStroke.getKeyStroke(KeyEvent.VK_V, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); editMenu.add(pasteMenuItem); textMenu = new JMenu("Text"); menuBar.add(textMenu); JMenu windowMenu = new JMenu("Window"); menuBar.add(windowMenu); initialize(); additionalSetup(); } private NoteTable newNoteTable() { return new NoteTable(new NoteTableModel(noteMngr)); } /** * Initialize the contents of the frame. The contents of this method was * generated by Window Builder Pro. */ private void initialize() { setTitle(buildTitle()); setBounds(100, 100, 450, 300); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); GridBagLayout gridBagLayout = new GridBagLayout(); gridBagLayout.columnWidths = new int[] { 0, 0, 0, 0 }; gridBagLayout.rowHeights = new int[] { 0, 0, 0, 0, 0, 0 }; gridBagLayout.columnWeights = new double[] { 0.0, 0.0, 1.0, Double.MIN_VALUE }; gridBagLayout.rowWeights = new double[] { 0.0, 1.0, 1.0, 1.0, 1.0, Double.MIN_VALUE }; getContentPane().setLayout(gridBagLayout); horizontalStrut = Box.createHorizontalStrut(20); horizontalStrut.setMinimumSize(new Dimension(3, 0)); horizontalStrut.setPreferredSize(new Dimension(3, 0)); horizontalStrut.setSize(new Dimension(3, 0)); GridBagConstraints gbc_horizontalStrut = new GridBagConstraints(); gbc_horizontalStrut.insets = new Insets(0, 0, 0, 0); gbc_horizontalStrut.gridx = 0; gbc_horizontalStrut.gridy = 0; getContentPane().add(horizontalStrut, gbc_horizontalStrut); GridBagConstraints gbc_stateImageLabel = new GridBagConstraints(); gbc_stateImageLabel.insets = new Insets(0, 0, 0, 0); gbc_stateImageLabel.anchor = GridBagConstraints.EAST; gbc_stateImageLabel.gridx = 1; gbc_stateImageLabel.gridy = 0; stateImageLabel.setBorder(null); stateImageLabel.setHorizontalAlignment(SwingConstants.CENTER); getContentPane().add(stateImageLabel, gbc_stateImageLabel); searchText = new JTextField(); GridBagConstraints gbc_searchText = new GridBagConstraints(); gbc_searchText.insets = new Insets(0, 0, 0, 0); gbc_searchText.fill = GridBagConstraints.HORIZONTAL; gbc_searchText.gridx = 2; gbc_searchText.gridy = 0; getContentPane().add(searchText, gbc_searchText); searchText.setColumns(10); if (SystemUtils.IS_OS_MAC_OSX) { // Make the text field look like the standard Mac OS X search // box control. searchText.putClientProperty("JTextField.variant", "search"); } splitPane = new JSplitPane(); splitPane.setBorder(null); splitPane.setOrientation(JSplitPane.VERTICAL_SPLIT); GridBagConstraints gbc_splitPane = new GridBagConstraints(); gbc_splitPane.gridwidth = 3; gbc_splitPane.gridheight = 4; gbc_splitPane.insets = new Insets(0, 0, 0, 0); gbc_splitPane.fill = GridBagConstraints.BOTH; gbc_splitPane.gridx = 0; gbc_splitPane.gridy = 1; getContentPane().add(splitPane, gbc_splitPane); notePanel = new NotePanel(); notePanel.getHuxleyUiManager().setOnTextChangeCallback(new Callback<Boolean>() { @Override public void callback(Boolean input) { toggleUnsavedChangeIndicator(input); } }); splitPane.setRightComponent(notePanel); noteTableScroll = new JScrollPane(); noteTableScroll.setMinimumSize(new Dimension(23, 100)); splitPane.setLeftComponent(noteTableScroll); noteTable = newNoteTable(); noteTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); noteTableScroll.setViewportView(noteTable); ActionObserverUtil.registerEnMass(actionMngr, observable, notePanel.getHuxleyUiManager().getTextAction(TextAction.INSERT_DATE)); insertDateMenuItem = new JMenuItem(actionMngr.getAction(InsertDynamicTextAction.class)); insertDateMenuItem.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, InputEvent.SHIFT_MASK | Toolkit.getDefaultToolkit().getMenuShortcutKeyMask())); textMenu.add(insertDateMenuItem); } private String buildTitle() { return "Kuebiko"; } /** * Perform additional setup to the frame. This is separate from the * initialize() method so that the GUI builder doesn't mess with it. */ private void additionalSetup() { mode = Mode.SEARCH; setFocusTraversalPolicy(new CustomFocusTraversalPolicy(searchText, notePanel)); // Search Text Field. AutoCompleteDecorator.decorate(searchText, noteMngr.getNoteTitles(), false); searchText.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "clear"); searchText.getDocument().addDocumentListener(new DocumentListener() { @Override public void changedUpdate(DocumentEvent e) { onSearchTextChanged(); } @Override public void insertUpdate(DocumentEvent e) { onSearchTextChanged(); } @Override public void removeUpdate(DocumentEvent e) { onSearchTextChanged(); } }); searchText.getActionMap().put("clear", new AbstractAction() { private static final long serialVersionUID = 1L; @Override public void actionPerformed(ActionEvent e) { searchText.setText(null); } }); searchText.addFocusListener(new FocusAdapter() { @Override public void focusGained(FocusEvent e) { setModeToSearch(); searchText.selectAll(); } @Override public void focusLost(FocusEvent e) { noteTable.selectNote(searchText.getText()); } }); searchText.addActionListener(actionMngr.getAction(NewNoteAction.class)); // Note Table. noteTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent event) { if (event.getValueIsAdjusting()) { return; } final Note selectedNote = noteTable.getSelectedNote(); if (selectedNote == null) { setModeToSearch(); } else { setModeToEdit(); notePanel.setNote(selectedNote); searchText.setText(selectedNote.getTitle()); } } }); } private void setModeToSearch() { mode = Mode.SEARCH; stateImageLabel.setIcon(new ImageIcon(KuebikoImageManager.get().getImage("search"))); noteTable.clearSelection(); observable.setChangedAndNotify(); } private void setModeToEdit() { mode = Mode.EDIT; stateImageLabel.setIcon(new ImageIcon(KuebikoImageManager.get().getImage("edit"))); observable.setChangedAndNotify(); } private void toggleUnsavedChangeIndicator(boolean unsavedChanges) { if (SystemUtils.IS_OS_MAC_OSX) { // Toggle the Mac OS X window-modified indicator. getRootPane().putClientProperty("Window.documentModified", unsavedChanges); } else { if (unsavedChanges) { setTitle("* " + buildTitle()); } else { setTitle(buildTitle()); } } } public boolean isNoteSelected() { return (noteTable == null ? false : (noteTable.getSelectedRow() != -1)); } Note getSelectedNote() { return noteTable.getSelectedNote(); } void deleteSelectedNote() { if (!isNoteSelected()) { throw new IllegalStateException("No note currently selected."); } noteTable.deleteSelectedNote(); searchText.setText(""); } /** * Handler for when the contents of the search text field changes. */ private void onSearchTextChanged() { // Only update the UI if the user actively searching. if (mode == Mode.SEARCH) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { noteTable.filter(searchText.getText()); notePanel.setNote(null); } }); } } boolean isInSearchMode() { return (mode == Mode.SEARCH); } boolean isInEditMode() { return (mode == Mode.EDIT); } JTextField getSearchText() { return searchText; } NoteTable getNoteTable() { return noteTable; } NotePanel getNotePanel() { return notePanel; } NoteManager getNoteMngr() { return noteMngr; } }